Skip to content

Menu

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.

  1. 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.
  2. 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.
  3. Read the answer. match.level is 5, "Same name and address". status is blocked because your level (stop_level) is 1 by default. Change the name to "Anne Jensen" and match.level becomes 0.

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.label and match.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).

Download OpenAPI (JSON)

Base URLhttps://api.robinsontjek.dk
First call
curl -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"
      }
    ]
  }'
Response · 200
{
  "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.

Header
Authorization: Bearer rl_test_4f9c1a7e2b8d6035
Wrong key
HTTP/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.

A person with no match
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"
      }
    ]
  }'
Response · 200
{
  "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.
A result
{
  "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 can't be read
{
  "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 · 422
{
  "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 (202 for a job) and the header Idempotent-Replayed: true.
  • The same key with different content returns 422 idempotency_conflict. While the first call is still running it returns 409 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.

Headers on a response
HTTP/1.1 200 OK
X-Robinsontjek-Mode: live
RateLimit-Limit: 600
RateLimit-Remaining: 597
RateLimit-Reset: 41
Content-Type: application/json
Call with Idempotency-Key
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"
      }
    ]
  }'
Too many calls
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_url covers one job. Pass it to POST /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.
  1. Read the header and take t and v1.
  2. Compute HMAC-SHA256 of t + "." + raw body with the secret.
  3. Compare with v1 using a constant-time comparison.
  4. Reject deliveries where t is 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.

Delivery
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"
  }
}
Verify the signature
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));
}
Secret for a callback_url
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");

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. status is computed from stop_level.
  • stop_level can be sent on POST /v2/check-jobs.
  • receipt_url on finished jobs.
  • RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every response.
  • Deprecated: level (unit, building …) in a result and policy in a request. Use match.level and stop_level.

People

Check people against the Robinson list. Up to 1,000 at a time with the answer straight away, or large lists as a job that runs in the background.

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-Keystring

    A 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 objectsrequired

    The people to check. At least 1 and at most 1,000.

    Show 12 fields
    • idstring

      Your own reference, such as the id in your CRM. It comes back in the response.

    • namestring

      Full name. Or use first_name and last_name.

    • first_namestring

      First name (and any middle names).

    • last_namestring

      Surname.

    • addressstring

      The whole address in one field, such as Stationsalleen 48, 1. b, 2770 Kastrup. Or use the fields below.

    • streetstring

      Street name.

    • house_numberstring

      House number, with a letter if there is one, such as 12B.

    • floorstring

      Floor, such as st (ground), 1 or kl (basement).

    • doorstring

      Door, such as tv (left), th (right), mf (middle) or b.

    • postal_codestring

      Postcode, four digits.

    • citystring

      Town.

    • phonestring

      Phone number, if you have it. Only used when phone lookups are switched on.

  • stop_levelinteger · 1–5

    Go 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_versionobjectrequired

    The edition of the Robinson list that was checked against.

    Show 4 fields
    • idstring (uuid)required

      The list version's id.

    • sourcestring

      cpr is the Robinson list. customer is your own list of people who asked not to be contacted.

    • published_onstring (date)required

      The date CPR published the list.

    • activated_atstring (date-time)

      When we put the list to use.

  • resultsarray of objectsrequired

    One result per person, in the same order as the request.

    Show 7 fields
    • idstring · can be null

      Your reference from the request.

    • matchobject

      What we found on the Robinson list at the person's address.

      Show 5 fields
      • levelinteger · 0–5 · can be nullrequired

        Level from 0 to 5. null when the address can't be read.

      • codestring · can be nullrequired

        The level as a code: none, household, surname, given_name, name_building or name_address.

      • labelstring · can be null

        The level in words, in Danish or English following Accept-Language.

      • onarray of strings

        The parts that matched: first_name, last_name, address and unit.

      • previous_addressboolean

        true when the person is on the list at a previous address. It counts as at least level 4.

    • statusobjectrequired

      For integrations, computed from your chosen level: allowed (below the level), blocked (at the level or above) or manual (the address can't be read).

    • stop_levelinteger · 1–5 · can be null

      The level you go by, which status is computed from. 1 by default, the most cautious.

    • reasonsarray of objectsrequired

      Codes that explain the answer. See Reasons.

    • addressobject · can be null

      How we read the address. null if it could not be read.

      Show 10 fields
      • inputstring

        The address as you sent it.

      • okboolean

        true when the address could be read.

      • streetstring · can be null

        Street name.

      • house_numberstring · can be null

        House number.

      • floorstring · can be null

        Floor.

      • doorstring · can be null

        Door.

      • postal_codestring · can be null

        Postcode.

      • citystring · can be null

        Town.

      • formattedstring · can be null

        The address written neatly on one line.

      • confidencestring

        exact, corrected or unresolved (could not be read).

    • phone_lookupobject · can be null

      Phone numbers only: what the directory lookup returned.

      Show 3 fields
      • outcomestring

        found, unlisted or not_found.

      • namestring

        The name behind the number.

      • addressstring

        The address behind the number.

Possible errors401402409422429503

POST /v2/checks
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"
      }
    ]
  }'
Response · 200 OK
{
  "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-Keystring

    A 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 objectsrequired

    The people to check. At most 1,000,000.

    Show 12 fields
    • idstring

      Your own reference, such as the id in your CRM. It comes back in the response.

    • namestring

      Full name. Or use first_name and last_name.

    • first_namestring

      First name (and any middle names).

    • last_namestring

      Surname.

    • addressstring

      The whole address in one field, such as Stationsalleen 48, 1. b, 2770 Kastrup. Or use the fields below.

    • streetstring

      Street name.

    • house_numberstring

      House number, with a letter if there is one, such as 12B.

    • floorstring

      Floor, such as st (ground), 1 or kl (basement).

    • doorstring

      Door, such as tv (left), th (right), mf (middle) or b.

    • postal_codestring

      Postcode, four digits.

    • citystring

      Town.

    • phonestring

      Phone number, if you have it. Only used when phone lookups are switched on.

  • callback_urlstring (uri)

    We send a signed POST here when the job is done or has failed. See Webhooks.

  • external_refstring

    Your own reference for the job, such as the campaign name. It comes back in responses and webhooks.

  • stop_levelinteger · 1–5

    The level this job is filtered on. Your own by default. See Match levels.

Or send a file (multipart/form-data)

  • filestring (binary)required

    An Excel file (xlsx or xls) or a CSV file.

  • column_mappingobject

    Which column is which. Leave it out and we find the columns ourselves.

    Show 15 fields
    • header_rowinteger

      The row with the headings. The first row is 0.

    • nameinteger | string

      The column's number (the first is 0) or its heading.

    • first_nameinteger | string

      The column's number (the first is 0) or its heading.

    • last_nameinteger | string

      The column's number (the first is 0) or its heading.

    • addressinteger | string

      The column's number (the first is 0) or its heading.

    • streetinteger | string

      The column's number (the first is 0) or its heading.

    • house_numberinteger | string

      The column's number (the first is 0) or its heading.

    • floorinteger | string

      The column's number (the first is 0) or its heading.

    • doorinteger | string

      The column's number (the first is 0) or its heading.

    • postal_codeinteger | string

      The column's number (the first is 0) or its heading.

    • cityinteger | string

      The column's number (the first is 0) or its heading.

    • phoneinteger | string

      The column's number (the first is 0) or its heading.

    • cvrinteger | string

      The column's number (the first is 0) or its heading.

    • company_nameinteger | string

      The column's number (the first is 0) or its heading.

    • external_idinteger | string

      The column's number (the first is 0) or its heading.

  • callback_urlstring (uri)

    We send a signed POST here when the job is done or has failed. See Webhooks.

  • external_refstring

    Your own reference for the job, such as the campaign name. It comes back in responses and webhooks.

  • stop_levelinteger · 1–5

    The level this job is filtered on. Your own by default. See Match levels.

Response202

The job is accepted and queued.

  • idstring (uuid)required

    The job's id.

  • sourcestringrequired

    Where the list came from, such as api, upload or email.

  • statusstringrequired

    queued, running, done or failed. draft is a job that has not been started yet.

    One of: draft, queued, running, done, failed, deleted

  • filenamestring · can be null

    The file's name, when the list came as a file.

  • file_formatstring · can be null

    xlsx, xls or csv.

  • row_countinteger

    Rows in the list.

  • processed_countinteger

    Rows checked so far.

  • allowed_countinteger

    How many are below your level.

  • blocked_countinteger

    How many are at your level or above.

  • manual_countinteger

    How many addresses could not be read.

  • level_countsobject
    Show 7 fields
    • 0integerrequired
    • 1integerrequired
    • 2integerrequired
    • 3integerrequired
    • 4integerrequired
    • 5integerrequired
    • unreadintegerrequired
  • column_mappingobject

    Which columns were used for what.

    Show 15 fields
    • header_rowinteger

      The row with the headings. The first row is 0.

    • nameinteger | string

      The column's number (the first is 0) or its heading.

    • first_nameinteger | string

      The column's number (the first is 0) or its heading.

    • last_nameinteger | string

      The column's number (the first is 0) or its heading.

    • addressinteger | string

      The column's number (the first is 0) or its heading.

    • streetinteger | string

      The column's number (the first is 0) or its heading.

    • house_numberinteger | string

      The column's number (the first is 0) or its heading.

    • floorinteger | string

      The column's number (the first is 0) or its heading.

    • doorinteger | string

      The column's number (the first is 0) or its heading.

    • postal_codeinteger | string

      The column's number (the first is 0) or its heading.

    • cityinteger | string

      The column's number (the first is 0) or its heading.

    • phoneinteger | string

      The column's number (the first is 0) or its heading.

    • cvrinteger | string

      The column's number (the first is 0) or its heading.

    • company_nameinteger | string

      The column's number (the first is 0) or its heading.

    • external_idinteger | string

      The column's number (the first is 0) or its heading.

  • optionsobject

    What the job checks.

    Show 4 fields
    • check_peopleboolean

      Check people against the Robinson list.

    • check_companiesboolean

      Check companies against the CVR register's marketing protection.

    • check_phonesboolean

      Look up phone numbers.

    • stop_levelinteger · 1–5

      The level the list is filtered on. Your own by default.

  • list_versionobject

    The edition of the Robinson list that was checked against.

    Show 4 fields
    • idstring (uuid)required

      The list version's id.

    • sourcestring

      cpr is the Robinson list. customer is your own list of people who asked not to be contacted.

    • published_onstring (date)required

      The date CPR published the list.

    • activated_atstring (date-time)

      When we put the list to use.

  • errorstring · can be null

    Why the job failed.

  • external_refstring · can be null

    Your own reference from when you created the job.

  • receipt_urlstring (uri) · can be null

    The address of the check receipt, once the job is done.

  • delete_afterstring (date-time) · can be null

    When the file and the rows are deleted.

  • created_atstring (date-time)

    When the job was created.

  • finished_atstring (date-time) · can be null

    When the job finished.

Possible errors401402409413422429503

POST /v2/check-jobs
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"
  }'
Response · 202 Accepted
{
  "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)required

    The job's id.

Response200

The job as it stands.

  • idstring (uuid)required

    The job's id.

  • sourcestringrequired

    Where the list came from, such as api, upload or email.

  • statusstringrequired

    queued, running, done or failed. draft is a job that has not been started yet.

    One of: draft, queued, running, done, failed, deleted

  • filenamestring · can be null

    The file's name, when the list came as a file.

  • file_formatstring · can be null

    xlsx, xls or csv.

  • row_countinteger

    Rows in the list.

  • processed_countinteger

    Rows checked so far.

  • allowed_countinteger

    How many are below your level.

  • blocked_countinteger

    How many are at your level or above.

  • manual_countinteger

    How many addresses could not be read.

  • level_countsobject
    Show 7 fields
    • 0integerrequired
    • 1integerrequired
    • 2integerrequired
    • 3integerrequired
    • 4integerrequired
    • 5integerrequired
    • unreadintegerrequired
  • column_mappingobject

    Which columns were used for what.

    Show 15 fields
    • header_rowinteger

      The row with the headings. The first row is 0.

    • nameinteger | string

      The column's number (the first is 0) or its heading.

    • first_nameinteger | string

      The column's number (the first is 0) or its heading.

    • last_nameinteger | string

      The column's number (the first is 0) or its heading.

    • addressinteger | string

      The column's number (the first is 0) or its heading.

    • streetinteger | string

      The column's number (the first is 0) or its heading.

    • house_numberinteger | string

      The column's number (the first is 0) or its heading.

    • floorinteger | string

      The column's number (the first is 0) or its heading.

    • doorinteger | string

      The column's number (the first is 0) or its heading.

    • postal_codeinteger | string

      The column's number (the first is 0) or its heading.

    • cityinteger | string

      The column's number (the first is 0) or its heading.

    • phoneinteger | string

      The column's number (the first is 0) or its heading.

    • cvrinteger | string

      The column's number (the first is 0) or its heading.

    • company_nameinteger | string

      The column's number (the first is 0) or its heading.

    • external_idinteger | string

      The column's number (the first is 0) or its heading.

  • optionsobject

    What the job checks.

    Show 4 fields
    • check_peopleboolean

      Check people against the Robinson list.

    • check_companiesboolean

      Check companies against the CVR register's marketing protection.

    • check_phonesboolean

      Look up phone numbers.

    • stop_levelinteger · 1–5

      The level the list is filtered on. Your own by default.

  • list_versionobject

    The edition of the Robinson list that was checked against.

    Show 4 fields
    • idstring (uuid)required

      The list version's id.

    • sourcestring

      cpr is the Robinson list. customer is your own list of people who asked not to be contacted.

    • published_onstring (date)required

      The date CPR published the list.

    • activated_atstring (date-time)

      When we put the list to use.

  • errorstring · can be null

    Why the job failed.

  • external_refstring · can be null

    Your own reference from when you created the job.

  • receipt_urlstring (uri) · can be null

    The address of the check receipt, once the job is done.

  • delete_afterstring (date-time) · can be null

    When the file and the rows are deleted.

  • created_atstring (date-time)

    When the job was created.

  • finished_atstring (date-time) · can be null

    When the job finished.

Possible errors401404429

GET /v2/check-jobs/{id}
curl https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58 \
  -H "Authorization: Bearer $ROBINSONTJEK_API_KEY"
Response · 200 OK
{
  "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)required

    The job's id.

Query

  • cursorstring

    next_cursor from the previous page. Leave it out for the first page.

  • limitinteger

    Results per page. At most 10,000.

Response200

A page of results.

  • resultsarray of objects

    The results on this page.

    Show 7 fields
    • idstring · can be null

      Your reference from the request.

    • matchobject

      What we found on the Robinson list at the person's address.

      Show 5 fields
      • levelinteger · 0–5 · can be nullrequired

        Level from 0 to 5. null when the address can't be read.

      • codestring · can be nullrequired

        The level as a code: none, household, surname, given_name, name_building or name_address.

      • labelstring · can be null

        The level in words, in Danish or English following Accept-Language.

      • onarray of strings

        The parts that matched: first_name, last_name, address and unit.

      • previous_addressboolean

        true when the person is on the list at a previous address. It counts as at least level 4.

    • statusobjectrequired

      For integrations, computed from your chosen level: allowed (below the level), blocked (at the level or above) or manual (the address can't be read).

    • stop_levelinteger · 1–5 · can be null

      The level you go by, which status is computed from. 1 by default, the most cautious.

    • reasonsarray of objectsrequired

      Codes that explain the answer. See Reasons.

    • addressobject · can be null

      How we read the address. null if it could not be read.

      Show 10 fields
      • inputstring

        The address as you sent it.

      • okboolean

        true when the address could be read.

      • streetstring · can be null

        Street name.

      • house_numberstring · can be null

        House number.

      • floorstring · can be null

        Floor.

      • doorstring · can be null

        Door.

      • postal_codestring · can be null

        Postcode.

      • citystring · can be null

        Town.

      • formattedstring · can be null

        The address written neatly on one line.

      • confidencestring

        exact, corrected or unresolved (could not be read).

    • phone_lookupobject · can be null

      Phone numbers only: what the directory lookup returned.

      Show 3 fields
      • outcomestring

        found, unlisted or not_found.

      • namestring

        The name behind the number.

      • addressstring

        The address behind the number.

  • next_cursorstring · can be null

    Send it as cursor to get the next page. null when there are no more.

Possible errors401404409429

GET /v2/check-jobs/{id}/results
curl https://api.robinsontjek.dk/v2/check-jobs/5b1d9e3c-2a4f-4c7e-8d16-0e9f7a2b4c58/results?limit=1000 \
  -H "Authorization: Bearer $ROBINSONTJEK_API_KEY"
Response · 200 OK
{
  "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 objectsrequired

    The companies to check. At most 1,000.

    Show 3 fields
    • idstring

      Your own reference. It comes back in the response.

    • cvrstring

      CVR number, eight digits.

    • namestring

      The company's name, if you don't have the CVR number.

Response200

One result per company.

  • resultsarray of objects

    One result per company.

    Show 7 fields
    • idstring · can be null

      Your reference from the request.

    • cvrstring · can be null

      The CVR number.

    • namestring · can be null

      The company's name in CVR.

    • statusobjectrequired

      blocked when the company has opted out of marketing. manual when we can't find it.

      One of: allowed, blocked, manual

    • reasonsarray of objectsrequired

      Codes that explain the answer, such as company_protected.

    • company_formstring · can be null

      Legal form, such as ApS or Enkeltmandsvirksomhed (sole proprietorship).

    • synced_atstring (date-time) · can be null

      When we last fetched the company from CVR.

Possible errors401402403422429

POST /v2/company-checks
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"
      }
    ]
  }'
Response · 200 OK
{
  "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"
    }
  ]
}

Phone numbers (add-on, by agreement)

Add-on, by agreement. Look a number up in the directory and check the person behind it against the Robinson list. The endpoint answers 403 until it is enabled for your account, and the price per lookup is agreed with you.

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 objectsrequired

    The numbers to look up. At most 500.

    Show 2 fields
    • phonestringrequired

      The phone number, such as +4520202020 or 20 20 20 20.

    • idstring

      Your own reference. It comes back in the response.

Response200

One result per number.

  • list_versionobject

    The edition of the Robinson list that was checked against.

    Show 4 fields
    • idstring (uuid)required

      The list version's id.

    • sourcestring

      cpr is the Robinson list. customer is your own list of people who asked not to be contacted.

    • published_onstring (date)required

      The date CPR published the list.

    • activated_atstring (date-time)

      When we put the list to use.

  • resultsarray of objects

    One result per number.

    Show 7 fields
    • idstring · can be null

      Your reference from the request.

    • matchobject

      What we found on the Robinson list at the person's address.

      Show 5 fields
      • levelinteger · 0–5 · can be nullrequired

        Level from 0 to 5. null when the address can't be read.

      • codestring · can be nullrequired

        The level as a code: none, household, surname, given_name, name_building or name_address.

      • labelstring · can be null

        The level in words, in Danish or English following Accept-Language.

      • onarray of strings

        The parts that matched: first_name, last_name, address and unit.

      • previous_addressboolean

        true when the person is on the list at a previous address. It counts as at least level 4.

    • statusobjectrequired

      For integrations, computed from your chosen level: allowed (below the level), blocked (at the level or above) or manual (the address can't be read).

    • stop_levelinteger · 1–5 · can be null

      The level you go by, which status is computed from. 1 by default, the most cautious.

    • reasonsarray of objectsrequired

      Codes that explain the answer. See Reasons.

    • addressobject · can be null

      How we read the address. null if it could not be read.

      Show 10 fields
      • inputstring

        The address as you sent it.

      • okboolean

        true when the address could be read.

      • streetstring · can be null

        Street name.

      • house_numberstring · can be null

        House number.

      • floorstring · can be null

        Floor.

      • doorstring · can be null

        Door.

      • postal_codestring · can be null

        Postcode.

      • citystring · can be null

        Town.

      • formattedstring · can be null

        The address written neatly on one line.

      • confidencestring

        exact, corrected or unresolved (could not be read).

    • phone_lookupobject · can be null

      Phone numbers only: what the directory lookup returned.

      Show 3 fields
      • outcomestring

        found, unlisted or not_found.

      • namestring

        The name behind the number.

      • addressstring

        The address behind the number.

Possible errors401402403422429503

POST /v2/phone-checks
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"
      }
    ]
  }'
Response · 200 OK
{
  "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"
      }
    }
  ]
}

List version

Which edition of the Robinson list checks run against right now.

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)required

    The list version's id.

  • sourcestring

    cpr is the Robinson list. customer is your own list of people who asked not to be contacted.

  • published_onstring (date)required

    The date CPR published the list.

  • activated_atstring (date-time)

    When we put the list to use.

Possible errors401429

GET /v2/list-versions/current
curl https://api.robinsontjek.dk/v2/list-versions/current \
  -H "Authorization: Bearer $ROBINSONTJEK_API_KEY"
Response · 200 OK
{
  "id": "4c8e2a1f-7d3b-4e6a-9b25-1f0c6d8e3a71",
  "source": "cpr",
  "published_on": "2026-10-01",
  "activated_at": "2026-10-02T05:00:00Z"
}

Addresses

Read Danish addresses written as free text and get them back split up and corrected. Free of charge.

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 stringsrequired

    The addresses as free text. At most 100.

Response200

One reading per address.

  • resultsarray of objects

    One reading per address, in the same order.

    Show 10 fields
    • inputstring

      The address as you sent it.

    • okboolean

      true when the address could be read.

    • streetstring · can be null

      Street name.

    • house_numberstring · can be null

      House number.

    • floorstring · can be null

      Floor.

    • doorstring · can be null

      Door.

    • postal_codestring · can be null

      Postcode.

    • citystring · can be null

      Town.

    • formattedstring · can be null

      The address written neatly on one line.

    • confidencestring

      exact, corrected or unresolved (could not be read).

Possible errors401422429

POST /v2/addresses/resolve
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"]
  }'
Response · 200 OK
{
  "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"
    }
  ]
}