Contents
Your first check
With the Robinsontjek API you check people against the Danish Robinson list straight from your own system, before you call or send addressed mail. You send a name and an address and get back exactly what we found for every person.
Every call goes to https://api.robinsontjek.dk and uses JSON. Error messages and match.label come in Danish or English following the Accept-Language header.
- Get a test key. Write to us and you get a key that starts with
rl_test_. It is free, needs no plan and never touches the real list. We reply within one working day. - Send one person. Put the key in an environment variable and run the call on the right. The person is called "Test Blokeret", so the answer is always the same.
- Read the answer.
match.levelis5, "Same name and address".statusisblockedbecause your level (stop_level) is 1 by default. Change the name to "Anne Jensen" andmatch.levelbecomes0.
When the answer looks right, swap in your live key (rl_live_…). Nothing else changes.
What you get for every person
match.level: what we found, from 0 (nobody on the list at the address) to 5 (same name and address). See the levels.match.code,match.labelandmatch.on: the same as a code and a text, and what matched.list_version: which edition of the Robinson list we checked against.
You decide which level you go by. For integrations that only want a yes or no, there is also status, computed from your chosen level (stop_level).
https://api.robinsontjek.dkcurl -X POST https://api.robinsontjek.dk/v2/checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"people": [
{
"id": "lead-1",
"name": "Test Blokeret",
"address": "Testvej 1, 9999 Testby"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
people: [
{
id: "lead-1",
name: "Test Blokeret",
address: "Testvej 1, 9999 Testby"
}
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
json={
"people": [
{
"id": "lead-1",
"name": "Test Blokeret",
"address": "Testvej 1, 9999 Testby"
}
]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
})
req.body = JSON.generate({
"people" => [
{
"id" => "lead-1",
"name" => "Test Blokeret",
"address" => "Testvej 1, 9999 Testby"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"people" => [
[
"id" => "lead-1",
"name" => "Test Blokeret",
"address" => "Testvej 1, 9999 Testby"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"list_version": {
"id": "00000000-0000-4000-8000-000000000001",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-01T06:00:00Z"
},
"results": [
{
"id": "lead-1",
"match": {
"level": 5,
"code": "name_address",
"label": "Same name and address",
"on": ["first_name", "last_name", "address", "unit"],
"previous_address": false
},
"status": "blocked",
"stop_level": 1,
"reasons": ["name_and_unit"],
"address": {
"input": "Testvej 1, 9999 Testby",
"ok": true,
"street": "Testvej",
"house_number": "1",
"floor": null,
"door": null,
"postal_code": "9999",
"city": "Testby",
"formatted": "Testvej 1, 9999 Testby",
"confidence": "exact"
}
}
]
}Authentication
Every call needs your API key in the Authorization header:
Authorization: Bearer rl_live_…
That is the only way to send the key. Keep it secret and use it only from your own server, never in a browser or an app. If the key is missing we answer 401 unauthorized. If it is wrong or revoked we answer 401 invalid_api_key.
If you need a new key, or an old one revoked, write to us.
Test and live
rl_live_…checks against the real Robinson list and counts towards your usage.rl_test_…returns fixed, made-up answers. See Test keys.
Every response has the header X-Robinsontjek-Mode, which is test or live, so you can see which key was used.
Authorization: Bearer rl_test_4f9c1a7e2b8d6035HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="Robinsontjek"
{
"error": {
"code": "invalid_api_key",
"message": "The API key is invalid or has been revoked.",
"request_id": "req_4a0f6c2e9b1d"
}
}Test keys
A test key (rl_test_…) lets you try the whole API without a plan, without cost and without touching a real list. Answers come from a small fixed set of made-up people, companies and numbers, so they are the same every time. Nothing is stored or counted towards usage. Every response carries X-Robinsontjek-Mode: test.
The list version is always the made-up 00000000-0000-4000-8000-000000000001, published 1 October 2026.
People
POST /v2/checks and POST /v2/check-jobs. The name (or the address) decides the answer, ignoring case:
| The name contains | match.level |
match.on |
Reason |
|---|---|---|---|
Test Blokeret |
5, same name and address | first name, last name, address, unit | name_and_unit |
Test Tidligere |
4, previous address (previous_address is true) |
first name, last name | previous_address |
Test Delvis |
2, same surname | last name, address | partial_name |
Test Manuel |
1, someone else in the household | address | household |
| anything else with an address we can read | 0 | none | none |
| anything else where the address can't be read | null |
none | address_unreadable |
status follows stop_level as it does live: blocked at or above it, allowed below. The address in the answer is read by the real address parser, so you can try that at the same time. An answer we can't decide is manual, never allowed.
Jobs
POST /v2/check-jobs with a test key takes JSON only (not a file) and at most 1,000 people. The job is done at once, with its level_counts, and its results are read from GET /v2/check-jobs/{id}/results. It expires after a day. A test key only sees test jobs, and a live key never sees them.
Companies and phone numbers (add-ons)
Both are available as add-ons by agreement. With a test key they work without an agreement, so you can build against them:
| Input | Answer |
|---|---|
CVR 99999999, or the name contains Test Blokeret |
blocked, company_protected |
CVR 99999998, or the name contains Test Manuel |
manual, address_not_found |
Phone 99999999 |
found, "Test Blokeret", level 5 |
Phone 99999998 |
found, "Test Manuel", level 1 |
Phone 99999997 |
unlisted: manual, phone_unlisted |
Phone 99999996 |
not found: manual, phone_not_found |
| anything else | allowed |
POST /v2/addresses/resolve, errors, validation and Idempotency-Key behave the same with either key.
curl -X POST https://api.robinsontjek.dk/v2/checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"people": [
{
"id": "lead-2",
"name": "Anne Jensen",
"address": "Testvej 1, 9999 Testby"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
people: [
{
id: "lead-2",
name: "Anne Jensen",
address: "Testvej 1, 9999 Testby"
}
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
json={
"people": [
{
"id": "lead-2",
"name": "Anne Jensen",
"address": "Testvej 1, 9999 Testby"
}
]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
})
req.body = JSON.generate({
"people" => [
{
"id" => "lead-2",
"name" => "Anne Jensen",
"address" => "Testvej 1, 9999 Testby"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"people" => [
[
"id" => "lead-2",
"name" => "Anne Jensen",
"address" => "Testvej 1, 9999 Testby"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"list_version": {
"id": "00000000-0000-4000-8000-000000000001",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-01T06:00:00Z"
},
"results": [
{
"id": "lead-2",
"match": {
"level": 0,
"code": "none",
"label": "Nobody on the list at the address",
"on": [],
"previous_address": false
},
"status": "allowed",
"stop_level": 1,
"reasons": [],
"address": {
"input": "Testvej 1, 9999 Testby",
"ok": true,
"street": "Testvej",
"house_number": "1",
"floor": null,
"door": null,
"postal_code": "9999",
"city": "Testby",
"formatted": "Testvej 1, 9999 Testby",
"confidence": "exact"
}
}
]
}Match levels
Every person gets a level from 0 to 5 in match.level. The level says what we found on the Robinson list at the person's address. The higher the level, the more closely what we found resembles the person.
| Level | match.code |
What we found |
|---|---|---|
| 0 | none |
Nobody on the list at the address |
| 1 | household |
Someone else in the household is on the list |
| 2 | surname |
Same surname at the address |
| 3 | given_name |
Same first name at the address |
| 4 | name_building |
Same name, different flat |
| 5 | name_address |
Same name and address |
If we can't read the address, match.level is null and the reason is address_unreadable. match.previous_address is true when the person is on the list at a previous address. It counts as at least level 4. match.on shows which parts matched.
Your level: status and stop_level
You choose the level you go by, once in your settings or per call with stop_level on POST /v2/checks and POST /v2/check-jobs. If you don't choose, we use level 1, the most cautious. The level comes back in every answer as stop_level, and from it we compute status for integrations that want a ready answer:
status |
When |
|---|---|
allowed |
match.level is below your level. |
blocked |
match.level is at your level or above. |
manual |
The address can't be read (match.level is null). Never allowed. |
status is a convenience. If you'd rather decide row by row, use match.level.
Reasons
reasons are codes you can translate and show yourself:
| Code | Means |
|---|---|
name_and_address |
The same name and address are on the list. |
name_and_unit |
The same name in the same flat is on the list. |
household |
Someone else at the address is on the list. |
partial_name |
A similar name is on the list at the address. |
previous_address |
The person is on the list at an address they have moved from. |
address_unreadable |
We could not read the address. Correct it and check again. |
address_not_found |
The address doesn't exist in Denmark as written. |
no_name |
There is no name. |
company_protected |
The company has opted out of marketing in CVR (add-on). |
phone_unlisted |
The number is unlisted (add-on). |
phone_not_found |
We could not find who has the number (add-on). |
list_not_ready |
The Robinson list is being updated. Check again shortly. |
{
"match": {
"level": 5,
"code": "name_address",
"label": "Same name and address",
"on": ["first_name", "last_name", "address", "unit"],
"previous_address": false
},
"status": "blocked",
"stop_level": 1,
"reasons": ["name_and_address"]
}{
"match": {
"level": null,
"code": null,
"label": null,
"on": [],
"previous_address": false
},
"status": "manual",
"stop_level": 1,
"reasons": ["address_unreadable"],
"address": null
}Errors
An error always has the same shape: an error object with a fixed code your code can act on, and a message you can show to a person. Where it applies, details says which field (field) and which row (index, from 0) is wrong. Include request_id if you write to us about an error.
| HTTP | code |
What to do |
|---|---|---|
| 400 | invalid_request |
The body isn't valid JSON. Fix it. |
| 401 | unauthorized |
The Authorization header is missing. |
| 401 | invalid_api_key |
The key is wrong or revoked. Write to us for a new one. |
| 402 | payment_required |
The account has no active plan. Write to us. Test keys never get this. |
| 403 | phone_not_enabled |
Phone lookups are available as an add-on by agreement and aren't enabled for you. |
| 403 | company_checks_not_enabled |
Company checks are available as an add-on by agreement and aren't enabled for you. |
| 404 | not_found |
It doesn't exist, or it belongs to another account. |
| 409 | job_not_finished |
The job isn't done yet. details.status is its status. Ask again shortly, or wait for callback_url. |
| 409 | idempotency_in_progress |
A call with the same Idempotency-Key is running. Wait Retry-After seconds (2). |
| 413 | payload_too_large |
The body or file is over 200 MB. Split the list. |
| 422 | invalid_request |
Something in the request is wrong, such as a person without an address. details says what. |
| 422 | idempotency_conflict |
The Idempotency-Key was used before for a different call. |
| 429 | rate_limited |
Too many calls. Wait the number of seconds in Retry-After. |
| 500 | internal_error |
Something failed on our side. Try again. |
| 503 | list_not_ready |
The Robinson list is being updated. Wait Retry-After seconds (300). |
Each endpoint lists under »Possible errors« which of these it can answer. Every call with a key can answer 401 and 429.
{
"error": {
"code": "invalid_request",
"message": "Person 3 has no address.",
"details": {
"field": "people",
"index": 3
},
"request_id": "req_7f2c9a41e0b3"
}
}Rate limits, idempotency and pages
There are two limits:
- Per key: 600 calls a minute.
- Per IP address: 3,000 calls a minute across all keys. It only bites if many systems share one address. Calls without a key count towards 300 a minute.
Every response to a call with a key carries three headers about the key limit: RateLimit-Limit (calls per minute), RateLimit-Remaining (calls left this minute) and RateLimit-Reset (seconds until the minute is over). Use them to slow down before you get an error. Go over a limit and we answer 429 rate_limited with Retry-After.
If you have many people, send them together: up to 1,000 in one call to POST /v2/checks, or up to 1,000,000 as a job with POST /v2/check-jobs. A job can be at most 200 MB.
Idempotency-Key
Send the Idempotency-Key header (at most 255 characters) with a unique value, such as a UUID, on POST /v2/checks and POST /v2/check-jobs. If the connection fails and you send the same call again with the same key, you get the first answer back instead of the list being checked a second time.
- A key is valid for 24 hours and belongs to the API key that used it.
- A repeated answer has the same status code as the first (
202for a job) and the headerIdempotent-Replayed: true. - The same key with different content returns
422 idempotency_conflict. While the first call is still running it returns409 idempotency_in_progress. - A call that got a 5xx error isn't remembered. You can send it again.
Pages of results
GET /v2/check-jobs/{id}/results answers with up to limit results (default 1,000, at most 10,000) and next_cursor. Send next_cursor as cursor in the next call, until it is null. A cursor is opaque: don't store it and don't build one yourself.
HTTP/1.1 200 OK
X-Robinsontjek-Mode: live
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 41
Content-Type: application/jsoncurl -X POST https://api.robinsontjek.dk/v2/checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: b3f1c2de-5a7e-4c41-9d0a-2f6e8a91c7d4" \
-d '{
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
people: [
{
id: "lead-1042",
name: "Anne Marie Jensen",
address: "Stationsalleen 48, 1. b, 2770 Kastrup"
}
]
}),
});
const data = await res.json();import os
import uuid
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
}
]
},
)
data = res.json()require "net/http"
require "json"
require "securerandom"
uri = URI("https://api.robinsontjek.dk/v2/checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
"Idempotency-Key" => SecureRandom.uuid,
})
req.body = JSON.generate({
"people" => [
{
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
"people" => [
[
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);HTTP/1.1 429 Too Many Requests
Retry-After: 12
RateLimit-Limit: 600
RateLimit-Remaining: 0
RateLimit-Reset: 12
{
"error": {
"code": "rate_limited",
"message": "Too many requests. Wait a moment and try again.",
"request_id": "req_1d8e4b07c2a9"
}
}Webhooks and callbacks
Robinsontjek sends a POST to your URL when something happens, so your system doesn't have to keep asking. There are two ways, and they use the same signature:
- Webhooks cover the whole account. Create the endpoint in the app under Settings. The signing secret is shown once, when the endpoint is created.
callback_urlcovers one job. Pass it toPOST /v2/check-jobs.
| Event | Happens when |
|---|---|
check_job.completed |
A job is done. Fetch the results. |
check_job.failed |
A job failed. data.error says why. |
saved_list.changed |
A saved list has changes after a new check. |
list_version.activated |
A new edition of the Robinson list has gone live. |
A webhook never carries personal data, only ids and counts. id is unique per delivery, so you can ignore a delivery you have already handled. data differs per event:
| Event | data |
|---|---|
check_job.completed, check_job.failed |
check_job_id, status, source, external_ref, row_count, allowed_count, blocked_count, manual_count, finished_at, and error on failed |
saved_list.changed |
saved_list_id, name, changes, newly_blocked, check_job_id, list_version_id |
list_version.activated |
list_version_id, published_on, source |
Verify the signature
Every delivery has the header Robinsontjek-Signature: t=<timestamp>,v1=<signature>. The signature is the hex HMAC-SHA256 of the text <timestamp>.<raw body>.
The secret depends on the way:
| Way | Secret |
|---|---|
| Webhook | The secret shown when the endpoint was created. |
callback_url |
The SHA-256 (hex) of the API key that created the job. You hold the key, so you can compute it yourself. |
- Read the header and take
tandv1. - Compute HMAC-SHA256 of
t + "." + raw bodywith the secret. - Compare with
v1using a constant-time comparison. - Reject deliveries where
tis more than 5 minutes old.
Use the raw body, not JSON that was parsed and written again. On a callback_url, Robinsontjek-Event is check_job.completed or check_job.failed, and the body is the same job as GET /v2/check-jobs/{id}.
Replies and retries
Reply with a 2xx status within 5 seconds. Anything else counts as a failure.
| Retries | |
|---|---|
| Webhook | Up to 8 attempts in all over about a day: after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours. |
callback_url |
4 attempts in all: after 1 minute, 10 minutes and 1 hour. Then we give up. The job can always be read with GET /v2/check-jobs/{id}. |
URLs that point into a private network are refused, and the URL must use https.
POST /hooks/robinsontjek HTTP/1.1
Content-Type: application/json
Robinsontjek-Signature: t=1792057351,v1=5d3c0b8e…
{
"id": "6f1c0c1e-93b4-4d2a-8e57-2c1f0a9b7d34",
"type": "check_job.completed",
"created_at": "2026-10-14T09:42:31Z",
"data": {
"check_job_id": "5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58",
"status": "done",
"source": "api",
"external_ref": "kampagne-oktober",
"row_count": 1840,
"allowed_count": 1512,
"blocked_count": 312,
"manual_count": 16,
"finished_at": "2026-10-14T09:42:30Z"
}
}import crypto from "node:crypto";
// rawBody: the request body exactly as it arrived (a Buffer or string)
function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return fresh && expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}import hashlib
import hmac
import time
def verify(header, raw_body: bytes, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
signed = parts["t"].encode() + b"." + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts["v1"])require "openssl"
def verify(header, raw_body, secret)
parts = header.split(",").to_h { |p| p.split("=", 2) }
expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{parts["t"]}.#{raw_body}")
fresh = (Time.now.to_i - parts["t"].to_i).abs < 300
fresh && Rack::Utils.secure_compare(expected, parts["v1"])
end<?php
function verify(string $header, string $rawBody, string $secret): bool {
parse_str(str_replace(",", "&", $header), $parts);
$expected = hash_hmac("sha256", $parts["t"] . "." . $rawBody, $secret);
$fresh = abs(time() - (int) $parts["t"]) < 300;
return $fresh && hash_equals($expected, $parts["v1"]);
}import crypto from "node:crypto";
// The secret for a callback_url is the SHA-256 (hex) of the API key that created the job.
const secret = crypto.createHash("sha256").update(process.env.ROBINSONTJEK_API_KEY).digest("hex");import hashlib
import os
secret = hashlib.sha256(os.environ["ROBINSONTJEK_API_KEY"].encode()).hexdigest()require "digest"
secret = Digest::SHA256.hexdigest(ENV.fetch("ROBINSONTJEK_API_KEY"))<?php
$secret = hash("sha256", getenv("ROBINSONTJEK_API_KEY"));Versions and changes
The API is versioned in the path (/v2). The list version (list_version) is something else: it says which edition of the Robinson list a check ran against.
- We add fields and endpoints without notice. Ignore fields you don't know.
- Changes that could break your code ship as a new version. We write to every key owner at least six months before the old version stops.
- Fields that are being retired keep working in that period.
Changes
14 October 2026
match(level,code,label,on,previous_address) comes first in every result.statusis computed fromstop_level.stop_levelcan be sent onPOST /v2/check-jobs.receipt_urlon finished jobs.RateLimit-Limit,RateLimit-RemainingandRateLimit-Reseton every response.- Deprecated:
level(unit,building…) in a result andpolicyin a request. Usematch.levelandstop_level.
Check people
POST/v2/checks
Checks up to 1,000 people and answers straight away, in the order you sent them. Give each person a name and an address. The address can be one field or split into street, house number, floor, door, postcode and town.
Headers
Idempotency-KeystringA unique value, such as a UUID. Send the same call again with the same key within 24 hours and you get the first answer back. See Rate limits and idempotency.
Body
peoplearray of objectsrequiredThe people to check. At least 1 and at most 1,000.
Show 12 fields
idstringYour own reference, such as the id in your CRM. It comes back in the response.
namestringFull name. Or use
first_nameandlast_name.first_namestringFirst name (and any middle names).
last_namestringSurname.
addressstringThe whole address in one field, such as
Stationsalleen 48, 1. b, 2770 Kastrup. Or use the fields below.streetstringStreet name.
house_numberstringHouse number, with a letter if there is one, such as
12B.floorstringFloor, such as
st(ground),1orkl(basement).doorstringDoor, such as
tv(left),th(right),mf(middle) orb.postal_codestringPostcode, four digits.
citystringTown.
phonestringPhone number, if you have it. Only used when phone lookups are switched on.
stop_levelinteger · 1–5Go by a different level than your organisation's for this call only. It changes
status, not what we found. See Match levels.
Response200
One result per person, in the same order.
list_versionobjectrequiredThe edition of the Robinson list that was checked against.
Show 4 fields
idstring (uuid)requiredThe list version's id.
sourcestringcpris the Robinson list.customeris your own list of people who asked not to be contacted.published_onstring (date)requiredThe date CPR published the list.
activated_atstring (date-time)When we put the list to use.
resultsarray of objectsrequiredOne result per person, in the same order as the request.
Show 7 fields
idstring · can be nullYour reference from the request.
matchobjectWhat we found on the Robinson list at the person's address.
Show 5 fields
levelinteger · 0–5 · can be nullrequiredLevel from 0 to 5.
nullwhen the address can't be read.codestring · can be nullrequiredThe level as a code:
none,household,surname,given_name,name_buildingorname_address.labelstring · can be nullThe level in words, in Danish or English following
Accept-Language.onarray of stringsThe parts that matched:
first_name,last_name,addressandunit.previous_addressbooleantruewhen the person is on the list at a previous address. It counts as at least level 4.
statusobjectrequiredFor integrations, computed from your chosen level:
allowed(below the level),blocked(at the level or above) ormanual(the address can't be read).stop_levelinteger · 1–5 · can be nullThe level you go by, which
statusis computed from. 1 by default, the most cautious.reasonsarray of objectsrequiredCodes that explain the answer. See Reasons.
addressobject · can be nullHow we read the address.
nullif it could not be read.Show 10 fields
inputstringThe address as you sent it.
okbooleantruewhen the address could be read.streetstring · can be nullStreet name.
house_numberstring · can be nullHouse number.
floorstring · can be nullFloor.
doorstring · can be nullDoor.
postal_codestring · can be nullPostcode.
citystring · can be nullTown.
formattedstring · can be nullThe address written neatly on one line.
confidencestringexact,correctedorunresolved(could not be read).
phone_lookupobject · can be nullPhone numbers only: what the directory lookup returned.
Show 3 fields
outcomestringfound,unlistedornot_found.namestringThe name behind the number.
addressstringThe address behind the number.
curl -X POST https://api.robinsontjek.dk/v2/checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: b3f1c2de-5a7e-4c41-9d0a-2f6e8a91c7d4" \
-d '{
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id": "lead-1043",
"first_name": "Jens",
"last_name": "Hansen",
"address": "Vestergade 12 8000 Aarhus"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
people: [
{
id: "lead-1042",
name: "Anne Marie Jensen",
address: "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
id: "lead-1043",
first_name: "Jens",
last_name: "Hansen",
address: "Vestergade 12 8000 Aarhus"
}
]
}),
});
const data = await res.json();import os
import uuid
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id": "lead-1043",
"first_name": "Jens",
"last_name": "Hansen",
"address": "Vestergade 12 8000 Aarhus"
}
]
},
)
data = res.json()require "net/http"
require "json"
require "securerandom"
uri = URI("https://api.robinsontjek.dk/v2/checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
"Idempotency-Key" => SecureRandom.uuid,
})
req.body = JSON.generate({
"people" => [
{
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id" => "lead-1043",
"first_name" => "Jens",
"last_name" => "Hansen",
"address" => "Vestergade 12 8000 Aarhus"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
"people" => [
[
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
],
[
"id" => "lead-1043",
"first_name" => "Jens",
"last_name" => "Hansen",
"address" => "Vestergade 12 8000 Aarhus"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"list_version": {
"id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-02T05:00:00Z"
},
"results": [
{
"id": "lead-1042",
"match": {
"level": 5,
"code": "name_address",
"label": "Same name and address",
"on": ["first_name", "last_name", "address", "unit"],
"previous_address": false
},
"status": "blocked",
"stop_level": 1,
"reasons": ["name_and_address"],
"address": {
"input": "Stationsalleen 48, 1. b, 2770 Kastrup",
"ok": true,
"street": "Stationsalleen",
"house_number": "48",
"floor": "1",
"door": "b",
"postal_code": "2770",
"city": "Kastrup",
"formatted": "Stationsalleen 48, 1. b, 2770 Kastrup",
"confidence": "exact"
}
},
{
"id": "lead-1043",
"match": {
"level": 0,
"code": "none",
"label": "Nobody on the list at the address",
"on": [],
"previous_address": false
},
"status": "allowed",
"stop_level": 1,
"reasons": [],
"address": {
"input": "Vestergade 12 8000 Aarhus",
"ok": true,
"street": "Vestergade",
"house_number": "12",
"floor": null,
"door": null,
"postal_code": "8000",
"city": "Aarhus C",
"formatted": "Vestergade 12, 8000 Aarhus C",
"confidence": "corrected"
}
}
]
}Create a job
POST/v2/check-jobs
For large lists: up to 1,000,000 people as JSON, or an Excel or CSV file. The job runs in the background. Follow it with »Get a job«, or be told at callback_url. Fetch the results page by page. The marked Excel file is in the app.
Headers
Idempotency-KeystringA unique value, such as a UUID. Send the same call again with the same key within 24 hours and you get the first answer back. See Rate limits and idempotency.
Body
peoplearray of objectsrequiredThe people to check. At most 1,000,000.
Show 12 fields
idstringYour own reference, such as the id in your CRM. It comes back in the response.
namestringFull name. Or use
first_nameandlast_name.first_namestringFirst name (and any middle names).
last_namestringSurname.
addressstringThe whole address in one field, such as
Stationsalleen 48, 1. b, 2770 Kastrup. Or use the fields below.streetstringStreet name.
house_numberstringHouse number, with a letter if there is one, such as
12B.floorstringFloor, such as
st(ground),1orkl(basement).doorstringDoor, such as
tv(left),th(right),mf(middle) orb.postal_codestringPostcode, four digits.
citystringTown.
phonestringPhone number, if you have it. Only used when phone lookups are switched on.
callback_urlstring (uri)We send a signed
POSThere when the job is done or has failed. See Webhooks.external_refstringYour own reference for the job, such as the campaign name. It comes back in responses and webhooks.
stop_levelinteger · 1–5The level this job is filtered on. Your own by default. See Match levels.
Or send a file (multipart/form-data)
filestring (binary)requiredAn Excel file (xlsx or xls) or a CSV file.
column_mappingobjectWhich column is which. Leave it out and we find the columns ourselves.
Show 15 fields
header_rowintegerThe row with the headings. The first row is 0.
nameinteger | stringThe column's number (the first is 0) or its heading.
first_nameinteger | stringThe column's number (the first is 0) or its heading.
last_nameinteger | stringThe column's number (the first is 0) or its heading.
addressinteger | stringThe column's number (the first is 0) or its heading.
streetinteger | stringThe column's number (the first is 0) or its heading.
house_numberinteger | stringThe column's number (the first is 0) or its heading.
floorinteger | stringThe column's number (the first is 0) or its heading.
doorinteger | stringThe column's number (the first is 0) or its heading.
postal_codeinteger | stringThe column's number (the first is 0) or its heading.
cityinteger | stringThe column's number (the first is 0) or its heading.
phoneinteger | stringThe column's number (the first is 0) or its heading.
cvrinteger | stringThe column's number (the first is 0) or its heading.
company_nameinteger | stringThe column's number (the first is 0) or its heading.
external_idinteger | stringThe column's number (the first is 0) or its heading.
callback_urlstring (uri)We send a signed
POSThere when the job is done or has failed. See Webhooks.external_refstringYour own reference for the job, such as the campaign name. It comes back in responses and webhooks.
stop_levelinteger · 1–5The level this job is filtered on. Your own by default. See Match levels.
Response202
The job is accepted and queued.
idstring (uuid)requiredThe job's id.
sourcestringrequiredWhere the list came from, such as
api,uploadoremail.statusstringrequiredqueued,running,doneorfailed.draftis a job that has not been started yet.One of:
draft,queued,running,done,failed,deletedfilenamestring · can be nullThe file's name, when the list came as a file.
file_formatstring · can be nullxlsx,xlsorcsv.row_countintegerRows in the list.
processed_countintegerRows checked so far.
allowed_countintegerHow many are below your level.
blocked_countintegerHow many are at your level or above.
manual_countintegerHow many addresses could not be read.
level_countsobjectShow 7 fields
0integerrequired1integerrequired2integerrequired3integerrequired4integerrequired5integerrequiredunreadintegerrequired
column_mappingobjectWhich columns were used for what.
Show 15 fields
header_rowintegerThe row with the headings. The first row is 0.
nameinteger | stringThe column's number (the first is 0) or its heading.
first_nameinteger | stringThe column's number (the first is 0) or its heading.
last_nameinteger | stringThe column's number (the first is 0) or its heading.
addressinteger | stringThe column's number (the first is 0) or its heading.
streetinteger | stringThe column's number (the first is 0) or its heading.
house_numberinteger | stringThe column's number (the first is 0) or its heading.
floorinteger | stringThe column's number (the first is 0) or its heading.
doorinteger | stringThe column's number (the first is 0) or its heading.
postal_codeinteger | stringThe column's number (the first is 0) or its heading.
cityinteger | stringThe column's number (the first is 0) or its heading.
phoneinteger | stringThe column's number (the first is 0) or its heading.
cvrinteger | stringThe column's number (the first is 0) or its heading.
company_nameinteger | stringThe column's number (the first is 0) or its heading.
external_idinteger | stringThe column's number (the first is 0) or its heading.
optionsobjectWhat the job checks.
Show 4 fields
check_peoplebooleanCheck people against the Robinson list.
check_companiesbooleanCheck companies against the CVR register's marketing protection.
check_phonesbooleanLook up phone numbers.
stop_levelinteger · 1–5The level the list is filtered on. Your own by default.
list_versionobjectThe edition of the Robinson list that was checked against.
Show 4 fields
idstring (uuid)requiredThe list version's id.
sourcestringcpris the Robinson list.customeris your own list of people who asked not to be contacted.published_onstring (date)requiredThe date CPR published the list.
activated_atstring (date-time)When we put the list to use.
errorstring · can be nullWhy the job failed.
external_refstring · can be nullYour own reference from when you created the job.
receipt_urlstring (uri) · can be nullThe address of the check receipt, once the job is done.
delete_afterstring (date-time) · can be nullWhen the file and the rows are deleted.
created_atstring (date-time)When the job was created.
finished_atstring (date-time) · can be nullWhen the job finished.
curl -X POST https://api.robinsontjek.dk/v2/check-jobs \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: b3f1c2de-5a7e-4c41-9d0a-2f6e8a91c7d4" \
-d '{
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id": "lead-1043",
"first_name": "Jens",
"last_name": "Hansen",
"address": "Vestergade 12 8000 Aarhus"
}
],
"callback_url": "https://example.com/hooks/robinsontjek",
"external_ref": "kampagne-oktober"
}'const res = await fetch("https://api.robinsontjek.dk/v2/check-jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
people: [
{
id: "lead-1042",
name: "Anne Marie Jensen",
address: "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
id: "lead-1043",
first_name: "Jens",
last_name: "Hansen",
address: "Vestergade 12 8000 Aarhus"
}
],
callback_url: "https://example.com/hooks/robinsontjek",
external_ref: "kampagne-oktober"
}),
});
const data = await res.json();import os
import uuid
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/check-jobs",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"people": [
{
"id": "lead-1042",
"name": "Anne Marie Jensen",
"address": "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id": "lead-1043",
"first_name": "Jens",
"last_name": "Hansen",
"address": "Vestergade 12 8000 Aarhus"
}
],
"callback_url": "https://example.com/hooks/robinsontjek",
"external_ref": "kampagne-oktober"
},
)
data = res.json()require "net/http"
require "json"
require "securerandom"
uri = URI("https://api.robinsontjek.dk/v2/check-jobs")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
"Idempotency-Key" => SecureRandom.uuid,
})
req.body = JSON.generate({
"people" => [
{
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
},
{
"id" => "lead-1043",
"first_name" => "Jens",
"last_name" => "Hansen",
"address" => "Vestergade 12 8000 Aarhus"
}
],
"callback_url" => "https://example.com/hooks/robinsontjek",
"external_ref" => "kampagne-oktober"
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/check-jobs");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
"Idempotency-Key: " . bin2hex(random_bytes(16)),
],
CURLOPT_POSTFIELDS => json_encode([
"people" => [
[
"id" => "lead-1042",
"name" => "Anne Marie Jensen",
"address" => "Stationsalleen 48, 1. b, 2770 Kastrup"
],
[
"id" => "lead-1043",
"first_name" => "Jens",
"last_name" => "Hansen",
"address" => "Vestergade 12 8000 Aarhus"
]
],
"callback_url" => "https://example.com/hooks/robinsontjek",
"external_ref" => "kampagne-oktober"
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"id": "5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58",
"source": "api",
"status": "queued",
"filename": null,
"file_format": null,
"row_count": 2,
"processed_count": 0,
"allowed_count": 0,
"blocked_count": 0,
"manual_count": 0,
"list_version": {
"id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-02T05:00:00Z"
},
"receipt_url": null,
"created_at": "2026-10-14T09:41:58Z",
"finished_at": null
}Get a job
GET/v2/check-jobs/{id}
Shows how far the job has come, and the counts once it is done. Once the job is done, receipt_url points to the check receipt.
Path
idstring (uuid)requiredThe job's id.
Response200
The job as it stands.
idstring (uuid)requiredThe job's id.
sourcestringrequiredWhere the list came from, such as
api,uploadoremail.statusstringrequiredqueued,running,doneorfailed.draftis a job that has not been started yet.One of:
draft,queued,running,done,failed,deletedfilenamestring · can be nullThe file's name, when the list came as a file.
file_formatstring · can be nullxlsx,xlsorcsv.row_countintegerRows in the list.
processed_countintegerRows checked so far.
allowed_countintegerHow many are below your level.
blocked_countintegerHow many are at your level or above.
manual_countintegerHow many addresses could not be read.
level_countsobjectShow 7 fields
0integerrequired1integerrequired2integerrequired3integerrequired4integerrequired5integerrequiredunreadintegerrequired
column_mappingobjectWhich columns were used for what.
Show 15 fields
header_rowintegerThe row with the headings. The first row is 0.
nameinteger | stringThe column's number (the first is 0) or its heading.
first_nameinteger | stringThe column's number (the first is 0) or its heading.
last_nameinteger | stringThe column's number (the first is 0) or its heading.
addressinteger | stringThe column's number (the first is 0) or its heading.
streetinteger | stringThe column's number (the first is 0) or its heading.
house_numberinteger | stringThe column's number (the first is 0) or its heading.
floorinteger | stringThe column's number (the first is 0) or its heading.
doorinteger | stringThe column's number (the first is 0) or its heading.
postal_codeinteger | stringThe column's number (the first is 0) or its heading.
cityinteger | stringThe column's number (the first is 0) or its heading.
phoneinteger | stringThe column's number (the first is 0) or its heading.
cvrinteger | stringThe column's number (the first is 0) or its heading.
company_nameinteger | stringThe column's number (the first is 0) or its heading.
external_idinteger | stringThe column's number (the first is 0) or its heading.
optionsobjectWhat the job checks.
Show 4 fields
check_peoplebooleanCheck people against the Robinson list.
check_companiesbooleanCheck companies against the CVR register's marketing protection.
check_phonesbooleanLook up phone numbers.
stop_levelinteger · 1–5The level the list is filtered on. Your own by default.
list_versionobjectThe edition of the Robinson list that was checked against.
Show 4 fields
idstring (uuid)requiredThe list version's id.
sourcestringcpris the Robinson list.customeris your own list of people who asked not to be contacted.published_onstring (date)requiredThe date CPR published the list.
activated_atstring (date-time)When we put the list to use.
errorstring · can be nullWhy the job failed.
external_refstring · can be nullYour own reference from when you created the job.
receipt_urlstring (uri) · can be nullThe address of the check receipt, once the job is done.
delete_afterstring (date-time) · can be nullWhen the file and the rows are deleted.
created_atstring (date-time)When the job was created.
finished_atstring (date-time) · can be nullWhen the job finished.
curl https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58 \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY"const res = await fetch("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58", {
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58")
req = Net::HTTP::Get.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"id": "5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58",
"source": "api",
"status": "done",
"filename": null,
"file_format": null,
"row_count": 1840,
"processed_count": 1840,
"allowed_count": 1512,
"blocked_count": 312,
"manual_count": 16,
"list_version": {
"id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-02T05:00:00Z"
},
"receipt_url": "https://robinsontjek.dk/kvittering/rk_9QeT4mXw2LbN7cVz",
"delete_after": "2026-11-13T09:42:30Z",
"created_at": "2026-10-14T09:41:58Z",
"finished_at": "2026-10-14T09:42:30Z"
}Get results
GET/v2/check-jobs/{id}/results
Results come in pages of up to 10,000. Send next_cursor from the response as cursor in the next call, until it is null.
Path
idstring (uuid)requiredThe job's id.
Query
cursorstringnext_cursorfrom the previous page. Leave it out for the first page.limitintegerResults per page. At most 10,000.
Response200
A page of results.
resultsarray of objectsThe results on this page.
Show 7 fields
idstring · can be nullYour reference from the request.
matchobjectWhat we found on the Robinson list at the person's address.
Show 5 fields
levelinteger · 0–5 · can be nullrequiredLevel from 0 to 5.
nullwhen the address can't be read.codestring · can be nullrequiredThe level as a code:
none,household,surname,given_name,name_buildingorname_address.labelstring · can be nullThe level in words, in Danish or English following
Accept-Language.onarray of stringsThe parts that matched:
first_name,last_name,addressandunit.previous_addressbooleantruewhen the person is on the list at a previous address. It counts as at least level 4.
statusobjectrequiredFor integrations, computed from your chosen level:
allowed(below the level),blocked(at the level or above) ormanual(the address can't be read).stop_levelinteger · 1–5 · can be nullThe level you go by, which
statusis computed from. 1 by default, the most cautious.reasonsarray of objectsrequiredCodes that explain the answer. See Reasons.
addressobject · can be nullHow we read the address.
nullif it could not be read.Show 10 fields
inputstringThe address as you sent it.
okbooleantruewhen the address could be read.streetstring · can be nullStreet name.
house_numberstring · can be nullHouse number.
floorstring · can be nullFloor.
doorstring · can be nullDoor.
postal_codestring · can be nullPostcode.
citystring · can be nullTown.
formattedstring · can be nullThe address written neatly on one line.
confidencestringexact,correctedorunresolved(could not be read).
phone_lookupobject · can be nullPhone numbers only: what the directory lookup returned.
Show 3 fields
outcomestringfound,unlistedornot_found.namestringThe name behind the number.
addressstringThe address behind the number.
next_cursorstring · can be nullSend it as
cursorto get the next page.nullwhen there are no more.
curl https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results?limit=1000 \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY"const res = await fetch("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results?limit=1000", {
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
params={
"limit": 1000
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results?limit=1000")
req = Net::HTTP::Get.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results?limit=1000");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"results": [
{
"id": "lead-1042",
"match": {
"level": 5,
"code": "name_address",
"label": "Same name and address",
"on": ["first_name", "last_name", "address", "unit"],
"previous_address": false
},
"status": "blocked",
"stop_level": 1,
"reasons": ["name_and_address"],
"address": {
"input": "Stationsalleen 48, 1. b, 2770 Kastrup",
"ok": true,
"street": "Stationsalleen",
"house_number": "48",
"floor": "1",
"door": "b",
"postal_code": "2770",
"city": "Kastrup",
"formatted": "Stationsalleen 48, 1. b, 2770 Kastrup",
"confidence": "exact"
}
},
{
"id": "lead-1043",
"match": {
"level": 0,
"code": "none",
"label": "Nobody on the list at the address",
"on": [],
"previous_address": false
},
"status": "allowed",
"stop_level": 1,
"reasons": [],
"address": {
"input": "Vestergade 12 8000 Aarhus",
"ok": true,
"street": "Vestergade",
"house_number": "12",
"floor": null,
"door": null,
"postal_code": "8000",
"city": "Aarhus C",
"formatted": "Vestergade 12, 8000 Aarhus C",
"confidence": "corrected"
}
}
],
"next_cursor": "eyJyb3ciOjEwMDB9"
}Companies (add-on, by agreement)
Add-on, by agreement. Check companies against the CVR register's marketing protection. If the company is a sole proprietorship, we also check the owner against the Robinson list. The endpoint answers 403 until it is enabled for your account. Write to us if you want it.
Check companies
POST/v2/company-checks
Add-on, by agreement: answers 403 until it is enabled for your account. Send the CVR number or the name of up to 1,000 companies at a time.
Body
companiesarray of objectsrequiredThe companies to check. At most 1,000.
Show 3 fields
idstringYour own reference. It comes back in the response.
cvrstringCVR number, eight digits.
namestringThe company's name, if you don't have the CVR number.
Response200
One result per company.
resultsarray of objectsOne result per company.
Show 7 fields
idstring · can be nullYour reference from the request.
cvrstring · can be nullThe CVR number.
namestring · can be nullThe company's name in CVR.
statusobjectrequiredblockedwhen the company has opted out of marketing.manualwhen we can't find it.One of:
allowed,blocked,manualreasonsarray of objectsrequiredCodes that explain the answer, such as
company_protected.company_formstring · can be nullLegal form, such as
ApSorEnkeltmandsvirksomhed(sole proprietorship).synced_atstring (date-time) · can be nullWhen we last fetched the company from CVR.
curl -X POST https://api.robinsontjek.dk/v2/company-checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"companies": [
{
"id": "acc-301",
"cvr": "12345678"
},
{
"id": "acc-302",
"name": "Eksempel Byg ApS"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/company-checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
companies: [
{
id: "acc-301",
cvr: "12345678"
},
{
id: "acc-302",
name: "Eksempel Byg ApS"
}
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/company-checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
json={
"companies": [
{
"id": "acc-301",
"cvr": "12345678"
},
{
"id": "acc-302",
"name": "Eksempel Byg ApS"
}
]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/company-checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
})
req.body = JSON.generate({
"companies" => [
{
"id" => "acc-301",
"cvr" => "12345678"
},
{
"id" => "acc-302",
"name" => "Eksempel Byg ApS"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/company-checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"companies" => [
[
"id" => "acc-301",
"cvr" => "12345678"
],
[
"id" => "acc-302",
"name" => "Eksempel Byg ApS"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"results": [
{
"id": "acc-301",
"cvr": "12345678",
"name": "Prøve Handel ApS",
"status": "blocked",
"reasons": ["company_protected"],
"company_form": "ApS",
"synced_at": "2026-10-14T03:00:00Z"
},
{
"id": "acc-302",
"cvr": "87654321",
"name": "Eksempel Byg ApS",
"status": "allowed",
"reasons": [],
"company_form": "ApS",
"synced_at": "2026-10-14T03:00:00Z"
}
]
}Check phone numbers
POST/v2/phone-checks
Add-on, by agreement: answers 403 until it is enabled for your account. Up to 500 numbers at a time, with or without +45 and spaces. If the number is unlisted, or we can't find who has it, the answer is manual.
Body
phonesarray of objectsrequiredThe numbers to look up. At most 500.
Show 2 fields
phonestringrequiredThe phone number, such as
+4520202020or20 20 20 20.idstringYour own reference. It comes back in the response.
Response200
One result per number.
list_versionobjectThe edition of the Robinson list that was checked against.
Show 4 fields
idstring (uuid)requiredThe list version's id.
sourcestringcpris the Robinson list.customeris your own list of people who asked not to be contacted.published_onstring (date)requiredThe date CPR published the list.
activated_atstring (date-time)When we put the list to use.
resultsarray of objectsOne result per number.
Show 7 fields
idstring · can be nullYour reference from the request.
matchobjectWhat we found on the Robinson list at the person's address.
Show 5 fields
levelinteger · 0–5 · can be nullrequiredLevel from 0 to 5.
nullwhen the address can't be read.codestring · can be nullrequiredThe level as a code:
none,household,surname,given_name,name_buildingorname_address.labelstring · can be nullThe level in words, in Danish or English following
Accept-Language.onarray of stringsThe parts that matched:
first_name,last_name,addressandunit.previous_addressbooleantruewhen the person is on the list at a previous address. It counts as at least level 4.
statusobjectrequiredFor integrations, computed from your chosen level:
allowed(below the level),blocked(at the level or above) ormanual(the address can't be read).stop_levelinteger · 1–5 · can be nullThe level you go by, which
statusis computed from. 1 by default, the most cautious.reasonsarray of objectsrequiredCodes that explain the answer. See Reasons.
addressobject · can be nullHow we read the address.
nullif it could not be read.Show 10 fields
inputstringThe address as you sent it.
okbooleantruewhen the address could be read.streetstring · can be nullStreet name.
house_numberstring · can be nullHouse number.
floorstring · can be nullFloor.
doorstring · can be nullDoor.
postal_codestring · can be nullPostcode.
citystring · can be nullTown.
formattedstring · can be nullThe address written neatly on one line.
confidencestringexact,correctedorunresolved(could not be read).
phone_lookupobject · can be nullPhone numbers only: what the directory lookup returned.
Show 3 fields
outcomestringfound,unlistedornot_found.namestringThe name behind the number.
addressstringThe address behind the number.
curl -X POST https://api.robinsontjek.dk/v2/phone-checks \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phones": [
{
"id": "lead-77",
"phone": "+4520202020"
}
]
}'const res = await fetch("https://api.robinsontjek.dk/v2/phone-checks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
phones: [
{
id: "lead-77",
phone: "+4520202020"
}
]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/phone-checks",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
json={
"phones": [
{
"id": "lead-77",
"phone": "+4520202020"
}
]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/phone-checks")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
})
req.body = JSON.generate({
"phones" => [
{
"id" => "lead-77",
"phone" => "+4520202020"
}
]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/phone-checks");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"phones" => [
[
"id" => "lead-77",
"phone" => "+4520202020"
]
]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"list_version": {
"id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-02T05:00:00Z"
},
"results": [
{
"id": "lead-77",
"match": {
"level": 0,
"code": "none",
"label": "Nobody on the list at the address",
"on": [],
"previous_address": false
},
"status": "allowed",
"stop_level": 1,
"reasons": [],
"phone_lookup": {
"outcome": "found",
"name": "Testa Testesen",
"address": "Prøvevej 12, 8000 Aarhus C"
}
}
]
}Get the current list
GET/v2/list-versions/current
Use it to show the list's date in your own system, or to see whether a new edition has gone live since you last looked.
Response200
The edition checks run against now.
idstring (uuid)requiredThe list version's id.
sourcestringcpris the Robinson list.customeris your own list of people who asked not to be contacted.published_onstring (date)requiredThe date CPR published the list.
activated_atstring (date-time)When we put the list to use.
curl https://api.robinsontjek.dk/v2/list-versions/current \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY"const res = await fetch("https://api.robinsontjek.dk/v2/list-versions/current", {
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
},
});
const data = await res.json();import os
import requests
res = requests.get(
"https://api.robinsontjek.dk/v2/list-versions/current",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/list-versions/current")
req = Net::HTTP::Get.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/list-versions/current");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
],
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
"source": "cpr",
"published_on": "2026-10-01",
"activated_at": "2026-10-02T05:00:00Z"
}Read addresses
POST/v2/addresses/resolve
Up to 100 addresses at a time. confidence says whether the address matched exactly (exact), was corrected (corrected) or could not be read (unresolved).
Body
addressesarray of stringsrequiredThe addresses as free text. At most 100.
Response200
One reading per address.
resultsarray of objectsOne reading per address, in the same order.
Show 10 fields
inputstringThe address as you sent it.
okbooleantruewhen the address could be read.streetstring · can be nullStreet name.
house_numberstring · can be nullHouse number.
floorstring · can be nullFloor.
doorstring · can be nullDoor.
postal_codestring · can be nullPostcode.
citystring · can be nullTown.
formattedstring · can be nullThe address written neatly on one line.
confidencestringexact,correctedorunresolved(could not be read).
curl -X POST https://api.robinsontjek.dk/v2/addresses/resolve \
-H "Authorization: Bearer $ROBINSONTJEK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"addresses": ["Stationsalleen 48 1b 2770 Kastrup", "Norregade 7, 1165 Kbh K"]
}'const res = await fetch("https://api.robinsontjek.dk/v2/addresses/resolve", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ROBINSONTJEK_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
addresses: ["Stationsalleen 48 1b 2770 Kastrup", "Norregade 7, 1165 Kbh K"]
}),
});
const data = await res.json();import os
import requests
res = requests.post(
"https://api.robinsontjek.dk/v2/addresses/resolve",
headers={
"Authorization": f"Bearer {os.environ['ROBINSONTJEK_API_KEY']}",
},
json={
"addresses": ["Stationsalleen 48 1b 2770 Kastrup", "Norregade 7, 1165 Kbh K"]
},
)
data = res.json()require "net/http"
require "json"
uri = URI("https://api.robinsontjek.dk/v2/addresses/resolve")
req = Net::HTTP::Post.new(uri, {
"Authorization" => "Bearer #{ENV.fetch("ROBINSONTJEK_API_KEY")}",
"Content-Type" => "application/json",
})
req.body = JSON.generate({
"addresses" => ["Stationsalleen 48 1b 2770 Kastrup", "Norregade 7, 1165 Kbh K"]
})
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)<?php
$ch = curl_init("https://api.robinsontjek.dk/v2/addresses/resolve");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ROBINSONTJEK_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"addresses" => ["Stationsalleen 48 1b 2770 Kastrup", "Norregade 7, 1165 Kbh K"]
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);{
"results": [
{
"input": "Stationsalleen 48 1b 2770 Kastrup",
"ok": true,
"street": "Stationsalleen",
"house_number": "48",
"floor": "1",
"door": "b",
"postal_code": "2770",
"city": "Kastrup",
"formatted": "Stationsalleen 48, 1. b, 2770 Kastrup",
"confidence": "corrected"
},
{
"input": "Norregade 7, 1165 Kbh K",
"ok": true,
"street": "Nørregade",
"house_number": "7",
"floor": null,
"door": null,
"postal_code": "1165",
"city": "København K",
"formatted": "Nørregade 7, 1165 København K",
"confidence": "corrected"
}
]
}