NAV
GraphQL

Introduction

Welcome to the Penneo KYC Public API. You can use our API to access our system via GraphQl, which can retrieve information on various cases, documents and clients from your organisation.

You can view code examples in the dark area to the right.

To use GraphQL, you must use the https://api.prod.kyc.penneo.com/public-api/graphql endpoint. They must be formatted correctly for graphql with query, variables and mutation fields if used.

We recommend using postman to try things out and copying the request as you preferred code using their built-in tools.

Remember to add the Authorization headers and X-API-Version-header with the appropriate version.

Versioning

The version is passed as part of the X-API-Version header


curl "api_endpoint_here"

  --header 'X-API-Version: 1'


var myHeaders = new Headers();

myHeaders.append("X-API-Version", "1");

var formdata = new FormData();

var requestOptions = {
  method: 'GET',
  headers: myHeaders,
  body: formdata,
  redirect: 'follow'
};

fetch("https://api.prod.kyc.penneo.com/public-api/graphql", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

The entire API, including the Authorize-endpoint, and GraphQL endpoints are versioned. The version is required on every request, and must be provided via the X-API-Version-header.

Supported versions

Version Release date Deprecation date
1 01-06-2020 6 months after version 2

Schema

You can find the entire graphql schema at:
https://api.prod.kyc.penneo.com/public-api/graphql/schema?version=1

Authentication

AccessId and AccessKey

To use the API you need a set of AccessId and AccessKey. They are obtained from within our application, in your settings, under “API Adgang“. If you cannot find this area in your settings, please contact support. They will be able to enable API access for you.

In the “API Adgang“-area you can press the button to obtain a new set of AccessId & AccessKey.

If you lose the key you can delete the Access Keypair via the “API Adgang“-area, where they are listed. The ID-variable corresponds to the AccessId in the list.

Authorization token

To get a token for the Authorization header, use this endpoint:

curl --location --request POST 'https://api.prod.kyc.penneo.com/public-api/authorize?accessId=<ACCESS-ID>&accessKey=<ACCESS-KEY>' \
    --header 'X-API-Version: 1' \
    --header 'Content-Type: application/javascript'
var myHeaders = new Headers();
myHeaders.append("X-API-Version", "1");
myHeaders.append("Content-Type", "application/javascript");

var formdata = new FormData();

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: formdata,
  redirect: 'follow'
};

fetch("https://api.prod.kyc.penneo.com/public-api/authorize?accessId=<ACCESS-ID>&accessKey=<ACCESS-KEY>", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));

The endpoint returns a JSON object with this structure:

{
    "token": "abcdefghijklmnopqrstuvwxyz",
    "timeExpiration": "2020-01-01T00:00:00+00:00"
}

The API is accessed via a Bearer token. This token is a standard JSON Web Token that is sent along with every request to our API via the Authorization-header. The token expires in 15 minutes, after which a new token must be obtained. It is recommend that you set up a way to automatically renew tokens when they expire if doing operations that span over 15 minutes.

To obtain a token you must use the authorize-endpoint:

POST https://api.prod.kyc.penneo.com/public-api/authorize?accessId=<ID>&accessKey=<KEY>

Parameter Description
accessId The ID of the Access Keypair
accessKey The Key of the Access Keypair

Using the token

The Authorization header, must be included on every request and be formatted as Bearer :

curl --location --request POST 'https://api.prod.kyc.penneo.com/public-api/grapqhl' \
    --header 'X-API-Version: 1' \
    --header 'Authorization: Bearer <TOKEN>' \
    --header 'Content-Type: application/json'
var myHeaders = new Headers();
myHeaders.append("X-API-Version", "1");
myHeaders.append("Authorization", "Bearer <TOKEN>");
myHeaders.append("Content-Type", "application/json");

var formdata = new FormData();

var requestOptions = {
  method: 'POST',
  headers: myHeaders,
  body: formdata,
  redirect: 'follow'
};

fetch("https://api.prod.kyc.penneo.com/public-api/graphql", requestOptions)
  .then(response => response.text())
  .then(result => console.log(result))
  .catch(error => console.log('error', error));
# The Authorization header is used on the request to the /graphql endpoint

You simply include the token in the Authorization header on every request, along with the X-API-Version-header.

Authorization: Bearer <YOUR-TOKEN>

Shared data types

The API uses custom data types which are not present in standard graphql:

Simple types

Timestamp

A UNIX timestamp version of a date and time. The value is in milliseconds.

Chunks

Json representation of a Chunk.

{
    "id": "12345678",
    "payload": "35633766",
    "payloadSize": 8,
    "mimeType": "text/plain",
    "subType": "ExternalId/DK/CVR/Number",
    "acceptance": "accepted", 
    "timeCreated": 12345678,
    "timeLastEdited": 12345678
}

Chunks are a generic type that most data in the API is based on, they all contain a type, and a subtype and the value is stored in the payload field. The payloads may be a file, for such cases the mimetype is included in the output.

The chunks have an acceptance value that indicates the acceptance status of a particular chunk.

Possible values for acceptance-field

For information on reading chunks from Companies and Persons, check under their section in the menu.

ExternalId_DK_CPR_Number

The CPR number in Denmark of a person

ExternalId_DK_CVR_Number

The VAT number in Denmark of the company

ExternalId_DK_CVR_APIId

The ApiId in the Danish VAT registry of the company or person

ExternalId_Vendor_Economic_CustomerNumber

An identifier in Economic for this a company or person

ContactInfo_Email

The contact email of a company or person

ContactInfo_Phone

The contact phonenumber of a company or person

IdentityValidationToken_DKNemIDCPR

An identity validation token from the danish NemID system.

Document

A document that has either been user-defined or defined by the system, could be a passport or a driving license.

ListCheck

A record of when we checked various PEP and sanctions lists for the name of the company or person.

Info Types

Info types describe a person or company that may or may not already exist in our database.

PersonInfo

Json representation of PersonInfo. You only need to supply one of the following pieces of information:

{
    "Name": "FirstName Lastname",
    "ExternalId_DK_CVR_APIId": ["XXXXXX"],
    "ExternalId_DK_CPR_Number": ["XXXXXXXX"],
    "ContactInfo_Email": ["XXXXXX"],
    "ContactInfo_Phone": ["XXXXXX"],
    "ExternalId_GenericCustomerNumber": ["XXXXXX"],
    "ExternalId_Vendor_Economic_CustomerNumber": ["XXXXXX"]
}

All fields in info, except Name, accepts multiple values.

You only need to supply one of these, but you should add all the info you can.

Field Description
Name The name of the person
ExternalId_DK_CVR_APIId The ApiId in the Danish VAT registry of the person, can be several
ExternalId_DK_CPR_Number The CPR number in Denmark of a person
ContactInfo_Email The contact email of the person, can be several
ContactInfo_Phone The contact phonenumber of the person, can be several
ExternalId_GenericCustomerNumber An identifier in your own system for this person, can be several
ExternalId_Vendor_Economic_CustomerNumber An identifier in your Economic system for this person, can be several

CompanyInfo

Json representation of CompanyInfo. You only need to supply one of the following pieces of information:

{
    "Name": "My Company Client",
    "ExternalId_DK_CVR_Number": ["XXXXXXXX"],
    "ExternalId_DK_CVR_APIId": ["XXXXXX"],
    "ContactInfo_Email": ["XXXXXX"],
    "ContactInfo_Phone": ["XXXXXX"],
    "ExternalId_GenericCustomerNumber": ["XXXXXX"],
    "ExternalId_Vendor_Economic_CustomerNumber": ["XXXXXX"],
    "ExternalId_Vendor_DunBradstreet_DUNSNumber": ["XXXXXX"],
    "ExternalId_NO_BusinessRegistrationNumber": ["XXXXXX"],
    "ExternalId_GenericBusinessRegistrationNumber": [
      {
        "countryCode": "DK",
        "registrationNumber": "XXXXXX"
      }
    ]
}

All fields in info, except Name, accepts multiple values.

You only need to supply one of these, but you should add all the info you can.

Field Description
Name The name of the company
ExternalId_DK_CVR_Number The VAT number in Denmark of the company, can be several
ExternalId_DK_CVR_APIId The ApiId in the Danish VAT registry of the company, can be several
ContactInfo_Email The contact email of the company, can be several
ContactInfo_Phone The contact phonenumber of the company, can be several
ExternalId_GenericCustomerNumber An identifier in your own system for this company, can be several
ExternalId_Vendor_Economic_CustomerNumber An identifier in your Economic system for this client, can be several
ExternalId_Vendor_DunBradstreet_DUNSNumber A DUNS number from DunBradstreet, can be several
ExternalId_NO_BusinessRegistrationNumber A Norwegian Business Registration Number, can be several
ExternalId_GenericBusinessRegistrationNumber A generic business number, you must supply both a country code and a registration number, can be several

Filtering

Each type filter is defined by its type, e.g. CaseFilters:

query getCases($amount: Int, $filters: [CaseFilters]) {
    cases(amount: $amount, filters: $filters) {
        items {
            item {
                id
                name
            }
        }
    }
}

variables: {
    filters: {
        { name: { equals: "test" } }
    }
}

We get the elements that matches the filter:

{
  "data": {
    "cases": {
      "items": [
        {
          "item": {
            "name": "test"
          }
        },
        {
          "item": {
            "name": "test"
          }
        }
      ],
    }
  }
}

The Public API has a powerful filtering system.

It allows you to filter either Cases, Companies or Persons.

The filtering system is currently only available for use with GraphQL.

In the code example to the right, you can see how a basic use of it looks. Here we are filtering cases for any case named "test".

The syntax of these filters allows each field to have multiple filters.

The operators are string based and can be seen alongside the filter definitions.

The various filters are described underneath:

CaseFilters

Name Type Supported operators
name String equals, notEquals
timeCreated Integer equals, notEquals, lessThan, greaterThan
timeLastEdited Integer equals, notEquals, lessThan, greaterThan
externalId String This has a special set of operators, and is explained in the next section. It looks up the related Persons and Companies as the cases do not have any externalIds.

CompanyFilters

Name Type Supported operators
name String equals, notEquals
timeCreated Integer equals, notEquals, lessThan, greaterThan
timeLastEdited Integer equals, notEquals, lessThan, greaterThan
externalId String This has a special set of operators, and is explained in the next section

PersonFilters

Name Type Supported operators
name String equals, notEquals
timeCreated Integer equals, notEquals, lessThan, greaterThan
timeLastEdited Integer equals, notEquals, lessThan, greaterThan
externalId String This has a special set of operators, and is explained in the next section

Special case: ExternalId

Again the filter syntax is almost the same, except for the externalId name and having two operators:

query getPersons($amount: Int, $filters: [PersonFilters]) {
    persons(amount: $amount, filters: $filters) {
        items {
            item {
                id
                name
                ExternalId_Vendor_Advosys_CustomerNumber
            }
        }
    }
}

variables {
    filters: {
        { externalId: { type: "ExternalId_Vendor_Advosys_CustomerNumber", in: ["1", "2", "3"] } }
    }
}

We get the elements that matches the filter:

{
  "data": {
    "persons": {
      "items": [
        {
          "item": {
            "name": "test",
            "ExternalId_Vendor_Advosys_CustomerNumber": ["1"]
          }
        },
        {
          "item": {
            "name": "test",
            "ExternalId_Vendor_Advosys_CustomerNumber": ["2"]
          }
        },
        {
          "item": {
            "name": "test",
            "ExternalId_Vendor_Advosys_CustomerNumber": ["3"]
          }
        }
      ]
    }
  }
}

The ExternalId filter allows API users to query cases persons or companies based on an id that is from another external system, like CVR or e-conomic.

It takes two operators that must both be present, the type, which is a string and an in-selection, which is an array of strings or integers.

Only Persons and Companies have ExternalIds. When filtering with this on cases, it looks into the related Persons and Companies of that case, and returns the case if matching relations were found.

Underneath you can see the ExternalIds that are currently supported by the API and the system:

Name Description
ExternalId_Vendor_Advosys_CustomerNumber A customer number from the Unik Advosys System
ExternalId_Vendor_Economic_CustomerNumber A customer number from Visma e-conomic
ExternalId_DK_CVR_APIId The unique id in the CVR API for this item
ExternalId_DK_CVR_Number The CVR number of a company.

Cases

Data model for cases

query {
  type Case implements Node {
    id: ID!
    name: String
    timeCreated: Timestamp!
    timeLastEdited: Timestamp!
    url: String
    status: String
    detailedStatus: String
    statusText: LocalizedSet
    clients(
      # Sets the amount of items to return. Defaults to 10
      amount: Int

      # The cursor of a previously returned item. The items returned will be ones listed before this cursor.
      before: String

      # The cursor of a previously returned item. The items returned will be ones listed after this cursor.
      after: String
    ): [Clients]
    AMLPersons(
      # Sets the amount of items to return. Defaults to 10
      amount: Int

      # The cursor of a previously returned item. The items returned will be ones listed before this cursor.
      before: String

      # The cursor of a previously returned item. The items returned will be ones listed after this cursor.
      after: String
    ): [Person]
    contactPersons(
      # Sets the amount of items to return. Defaults to 10
      amount: Int

      # The cursor of a previously returned item. The items returned will be ones listed before this cursor.
      before: String

      # The cursor of a previously returned item. The items returned will be ones listed after this cursor.
      after: String
    ): [Person]
    documents: [Document]
    riskAssessmentConclusion: RiskAssessmentConclusion
    riskAssessmentAnswers: [SimpleRAAnswer]
  }
}

You can see the datamodel for cases to the right.

The url is the url in the administration panel for this case.

The status is an enum-ish value of the current ultimate status. The detailedStatus is a more detailed status, still represented as an enum-ish value. The statusText is a localized string, (currently only available in danish), that translates the detailedStatus.

The possible values are listed below:

status detailedStatus Danish statusText
FULFILLED fulfilled No text on this one
CONTACTED contacted Can be either '1 person kontaktet' or '(number) personer kontaktet'
PENDING noClient Mangler klient
PENDING noOwners Mangler reelle ejere
PENDING noApproval Bekræft ejerforhold, før du fortsætter
PENDING noRiskAssessment Mangler risikovurdering
PENDING updateConclusion Konklusion bør opdateres
PENDING chunksPending Dokumenter afventer godkendelse
PENDING awaitingContact Afventer kontakt
PENDING abandonedContactPoint Kontaktet, men har ikke svaret

Create case

mutation CreateCase($preset: String!) {
  createCase(preset: $preset) {
    id
    name
    timeCreated
    timeLastEdited
  }
}

variables {
    preset: "somePreset"
}

Adding a case returns the following:

{
  "id": "abcdefg",
  "name": null,
  "timeCreated": 12345678,
  "timeLastEdited": 12345678
}

This mutation adds a case.

Variables

The preset variable does not do anything yet, and can be left out.

Variable Description
Preset The name of the a preset to use for this case, it is optional.

Get all Cases

query getCases($amount: Int) {
    cases(amount: amount) {
        items {
            item {
                id
                name
            }

            cursor
        }

        totalCount

        pageInfo {
            hasPreviousPage
            beforeCursor
            hasNextPage
            afterCursor
        }
    }
}

variables {
    amount: 10,
}

This query retrieves all cases.

This query is paginated. You can learn more about how this works in the Pagination section.

Get a case by ID

query  getCase($id: ID) {
    case(id: $id) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific case.

Variables

Variable Description
ID The ID of the case to retrieve

Risk-assessments

Add risk-assessment conclusion to case

mutation SetRiskAssessmentConclusion($caseId: ID, $conclusion: RiskAssessmentConclusion!, $notes: String) {
  setRiskAssessmentConclusion(caseId: $caseId, conclusion: $conclusion, notes: $notes) {
    id
    name
    riskAssessmentConclusion
    timeCreated
    timeLastEdited
  }
}

variables {
  caseId: "<CASE-ID>",
  conclusion: "LOW",
  notes: "My optional note"
}

This mutation adds a risk-assessment conclusion to a case. It requires a caseId and a RiskAssessmentConclusion. Optionally you can add a note as well.

Variables

Variable Description
caseId The id of the case.
conclusion The conclusion of the risk-assessment. See possible values below
Notes A note explaining the risk-assessment, it is optional.

Possible RiskAssessmentConclusion values

The preset variable does not do anything yet, and can be left out.

Value Description
UNKNOWN Used to set an unknown risk assessment.
LOW Used to set a low risk assessment.
MEDIUM Used to set a medium risk assessment
HIGH Used to set a high risk assessment

Companies

You can see the data model for companies to the right.

Data model for companies

query {
    type Company implements Node {
        id: ID!
        name: String
        timeCreated: Timestamp!
        timeLastEdited: Timestamp!
        url: String
        beneficialOwners: [Person]!
        legalOwners: [LegalOwners]
        executives: [Person]!
        ceo: [Person]!
        fullyResponsibleParticipants: [Person]!
        boardMembers: [Person]!
        boardChairman: [Person]!
        documents: [Document]
        ContactInfo_Email: [Email]
        ContactInfo_Phone: [PhoneNumber]
        CompanyInformation_Status: [Status]
        CompanyInformation_Type: [Type]
        CompanyInformation_PowerToBind: [PowerToBind]
        IdentityValidationToken: [IdentityValidationToken]
        ExternalId_Vendor_Economic_CustomerNumber: [EconomicCustomerNumber]
        ExternalId_Vendor_Advosys_CustomerNumber: [AdvosysCustomerNumber]
        ExternalId_DK_CVR_APIId: [DKCVRAPIIdType]
        ExternalId_DK_CVR_Number: [DKCVRNumber]
        ExternalId_Vendor_DunBradstreet_DUNSNumber: [Vendor_DunBradstreet_DUNSNumber]
        ExternalId_NO_BusinessRegistrationNumber: [NO_BusinessRegistrationNumber]
        ExternalId_GenericCustomerNumber: [GenericCustomerNumber]
        ExternalId_GenericBusinessRegistrationNumber: [GenericBusinessRegistrationNumber]
        listChecks: [ListCheck]
        latestListCheck: ListCheck
        externalQuestionnaireResponses: [ExternalQuestionnaireResponse]
    }
}

Add Company as a Client

mutation AddCompanyClientToCase($id: ID!, $info: CompanyInfo!) {
    addCompanyClientToCase(id: $id, info: $info) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
    info: See info example under "Shared data types"
}

This mutation adds a company with a relation as client to a specific case. You can set different information pieces on the client.

The system can deduce if a client already exists with the info you are adding. If so, any new information will be appended to the existing company client.

Variables

Variable Description
ID The ID of the case to add the client to

The company info variables can be found under Shared data types

Add Owner to a Company

mutation AddOwnerToCompany($id: ID!, $info: PersonInfo!) {
    addOwnerToCompany(id: $id, info: $info) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
    info: See PersonInfo example under "Shared data types"
}

This mutation adds an owner to a company. The owner will always be a person. You can set different information pieces on the client.

The system can deduce if an owner already exists with the info you are adding. If so, any new information will be appended to the existing company owner.

Variables

Variable Description
ID The ID of the company to add the company to

The PersonInfo variables can be found under Shared data types

Get all companies

query {
    companies(amount: amount) {
        items {
            item {
                id
                name
            }

            cursor
        }

        totalCount

        pageInfo {
            hasPreviousPage
            beforeCursor
            hasNextPage
            afterCursor
        }
    }
}

variables {
    amount: 10,
}

This query retrieves all companies.

This query is paginated. You can learn more about how this works in the Pagination section.

Get a company by ID

query {
    company(id: ID) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific company.

Variables

Variable Description
ID The ID of the company to retrieve

Get owners of a company

query getCompanyOwners($id: ID!) {
    company(id: $id) {
        id
        name

        beneficialOwners {
            id
            name
        }

        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific company's owners.

Variables

Variable Description
ID The ID of the company to retrieve owners for

Get chunks from a company

query getCompanyDocuments($id: ID!) {
    company(id: $id) {
        documents {
            id
            payload
            subType
            payloadSize
            mimeType
            timeCreated
            timeLastEdited
        }
    }
}

variables {
    id: ID,
}

See the Shared Data Types section for an explanation of Chunks.

Companies can have several types of Chunks associated with them. They are listed below with their Chunk type and their corresponding field on the Company type:

Type GraphQL
Document documents
Email ContactInfo_Email
PhoneNumber ContactInfo_Phone
IdentityValidationToken IdentityValidationToken
EconomicCustomerNumber ExternalId_Vendor_Economic_CustomerNumber
GenericCustomerNumber ExternalId_GenericCustomerNumber
GenericBusinessRegistrationNumber ExternalId_GenericBusinessRegistrationNumber
DKCVRApiIds ExternalId_DK_CVR_APIId
DKCVRNumbers ExternalId_DK_CVR_Number
ListCheck listChecks
ListCheck latestListCheck

Variables

Variable Description
ID The ID of the company to retrieve chunks for

Search for companies that are not already stored in the system

query($queryString: String!, $countryCode: String!) {
    lookupCompany(queryString: $queryString, countryCode: $countryCode) {
        items {
            item {
                Name
                ExternalId_DK_CVR_Number
                ExternalId_NO_BusinessRegistrationNumber
                ExternalId_Vendor_DunBradstreet_DUNSNumber
                score
            }
        }

        totalCount
    }
}

variables {
    queryString: String,
    countryCode: String
}

You can search for companies that the system might not already know about using the lookupCompany mutation. This will perform a search in connected systems like the Danish company registry (CVR) and, if you have an applicable subscription, Dun & Bradstreet's international company data registry.

The resulting list can be displayed to a user as a list of possible options.

Search results are effectively equivalent to a list of CompanyInfo objects, which means that a search result can be passed verbatim as input to the addCompanyClientToCase mutation.

Persons

You can see the datamodel for persons to the right.

Data model for persons

query {
    person {
        id: ID!
        name: String
        timeCreated: Timestamp!
        timeLastEdited: Timestamp!
        url: String
        beneficialOwners: [Person]!
        legalOwners: [LegalOwners]
        executives: [Person]!
        ceo: [Person]!
        fullyResponsibleParticipants: [Person]!
        boardMembers: [Person]!
        boardChairman: [Person]!
        documents: [Document]
        ContactInfo_Email: [Email]
        ContactInfo_Phone: [PhoneNumber]
        CompanyInformation_Status: [Status]
        CompanyInformation_Type: [Type]
        CompanyInformation_PowerToBind: [PowerToBind]
        IdentityValidationToken: [IdentityValidationToken]
        ExternalId_Vendor_Economic_CustomerNumber: [EconomicCustomerNumber]
        ExternalId_Vendor_Advosys_CustomerNumber: [AdvosysCustomerNumber]
        ExternalId_DK_CVR_APIId: [DKCVRAPIIdType]
        ExternalId_DK_CVR_Number: [DKCVRNumber]
        ExternalId_Vendor_DunBradstreet_DUNSNumber: [Vendor_DunBradstreet_DUNSNumber]
        ExternalId_NO_BusinessRegistrationNumber: [NO_BusinessRegistrationNumber]
        ExternalId_GenericCustomerNumber: [GenericCustomerNumber]
        listChecks: [ListCheck]
        latestListCheck: ListCheck
        externalQuestionnaireResponses: [ExternalQuestionnaireResponse]
    }
}

Add Person as a Client

mutation AddPersonClientToCase($id: ID!, $info: PersonInfo!) {
    addPersonClientToCase(id: $id, info: $info) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
    info: See info example under "Shared data types"
}

This endpoint adds a person with a relation as client, to a specific case. You can set different information pieces on the client.

The system can deduce if a client already exists with the info you are adding. If so, any new information will be appended to the existing person client.

The definition for PersonInfo variables can be found under "Shared data types"

Get all persons

query {
    persons(amount: amount) {
        items {
            item {
                id
                name
            }

            cursor
        }

        totalCount

        pageInfo {
            hasPreviousPage
            beforeCursor
            hasNextPage
            afterCursor
        }
    }
}

variables  {
    amount: 10,
}

This query retrieves all persons.

This query is paginated. You can learn more about how this works in the Pagination section.

Get a person by ID

query {
    person(id: ID) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific person.

Variables

Variable Description
ID The ID of the person to retrieve

Get chunks from a person

query getPersonDocuments($id: ID!) {
    person(id: $id) {
        documents {
            id
            payload
            subType
            payloadSize
            mimeType
            timeCreated
            timeLastEdited
        }
    }
}

variables {
    id: ID,
}

See the shared data types for an explanation of Chunks.

Persons have several types of chunks, they are listed below with their chunk type, and their graphql field. They have the exact same fields

Type and corresponding GraphQL field

Type GraphQL
Document documents
Email ContactInfo_Email
PhoneNumber ContactInfo_Phone
IdentityValidationToken IdentityValidationToken
EconomicCustomerNumber ExternalId_Vendor_Economic_CustomerNumber
GenericCustomerNumber ExternalId_GenericCustomerNumber
DKCVRApiIds ExternalId_DK_CVR_APIId
DKCPRNumbers ExternalId_DK_CPR_Number
ListCheck listChecks
ListCheck latestListCheck

Variables

Variable Description
ID The ID of the person to retrieve chunks for

Add a contact-person to a Case

mutation AddContactPersonToCase($id: ID!, $info: PersonInfo!) {
    addContactPersonToCase(id: $id, info: $info) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
    info: See info example under "Shared data types"
}

This mutation adds a contact-person with a relation as contact, to a specific case. You can set different information pieces on the person.

The system can deduce if a contact-person already exists with the info you are adding. If so, any new information will be appended to the existing contact-person.

Variables

Variable Description
ID The ID of the case to add the person to

The definition for PersonInfo variables can be found under "Shared data types"

Get all contact-persons for a Case

query getContactPersons($id: ID!) {
    case(id: $id) {
        id
        name

        contactPersons {
            id
            name
        }

        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific case's contact-persons.

Variables

Variable Description
ID The ID of the case to retrieve contact-persons for

Client Mutations

Here you can see mutations that are shared for both the Person client type, and the Company client type.

Set preferred language on person or company

mutation setPreferredLanguageForCompanyOrPerson($id: ID!, $language: String!) {
  setPreferredLanguageForCompanyOrPerson(id: $id, language: $language) {
    id
    name
    timeCreated
    timeLastEdited
  }
}

variables {
    id: ID!,
    language: String!,
}

This mutation sets the preferred language on person or company.

It can currently set these languages: - da for Danish - en for English - de for German - no for Norwegian

Variables

Variable Description
id The id of the company or person to set the language for
language The language to set on the person or company,

Contact-persons

Contact-persons are identical to persons, they only differ in their relation to the case and/or company.

Add a contact-person to a Case

mutation AddContactPersonToCase($id: ID!, $info: PersonInfo!) {
    addContactPersonToCase(id: $id, info: $info) {
        id
        name
        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
    info: See info example under "Shared data types"
}

This mutation adds a contact-person with a relation as contact, to a specific case. You can set different information pieces on the person.

The system can deduce if a contact-person already exists with the info you are adding. If so, any new information will be appended to the existing contact-person.

Variables

Variable Description
ID The ID of the case to add the person to

The definition for PersonInfo variables can be found under "Shared data types"

Get all contact-persons for a Case

query getContactPersons($id: ID!) {
    case(id: $id) {
        id
        name

        contactPersons {
            id
            name
        }

        timeCreated
        timeLastEdited
    }
}

variables {
    id: ID,
}

This query retrieves a specific case's contact-persons.

Variables

Variable Description
ID The ID of the case to retrieve contact-persons for

Contact-points

Contact-points are email or text messages sent to your clients. They include a link to Reach where documents and personal information can be entered securely.

Send ContactPoint Via Email

mutation sendContactPointEmail($contactInfoId: ID!, $caseId: ID!) {
    sendContactPointEmail(contactInfoId: $contactInfoId, caseId: $caseId) {
        token
        url
        type
        timeCreated
        timeLastEdited
    }
}

variables {
    contactInfoId: ID,
    caseId: ID
}

This mutation sends a contactpoint to an email for a case. Not that you need the ID of the Case and the ContactInfo/Email Chunk.

Variables

Variable Description
contactInfoId The ID of the ContactInfo/Email chunk
caseId The ID of the case to send the ContactPoint for

The definition for Chunk type can be found under "Shared data types"

Send ContactPoint Via Phone

mutation sendContactPointPhone($contactInfoId: ID!, $caseId: ID!) {
    sendContactPointPhone(contactInfoId: $contactInfoId, caseId: $caseId) {
        token
        url
        type
        timeCreated
        timeLastEdited
    }
}

variables {
    contactInfoId: ID,
    caseId: ID
}

This mutation sends a contactpoint to an email for a case. Not that you need the ID of the Case and the ContactInfo/Phone Chunk.

Variables

Variable Description
contactInfoId The ID of the ContactInfo/Phone chunk
caseId The ID of the case to send the ContactPoint for

The definition for Chunk type can be found under "Shared data types"

Create a contactpoint without sending it

mutation createReachLink($caseId: ID!) {
    createReachLink(caseId: $caseId) {
        token
        url
        type
        timeCreated
        timeLastEdited
    }
}

variables {
    caseId: ID
}

This mutation does not send any emails or phone messages, you get a link you can embed or provide to the client as you see fit.

Variables

Variable Description
caseId The ID of the case to create the ContactPoint for

Pagination

The initial request looks like this. We only get 5 elements and are thus on the first page:

query getCases($amount: Int) {
    cases(amount: $amount) {
        items {
            item {
                id
                name
            }

            cursor
        }

        totalCount

        pageInfo {
            hasPreviousPage
            beforeCursor
            hasNextPage
            afterCursor
        }
    }
}

variables {
    amount: 6
}

We get the elements with cursors, and page info:

{
  "data": {
          "cases": {
              "items": [
                  {
                      "item": {},
                      "cursor": "1"
                  },
                  {
                      "item": {},
                      "cursor": "2"
                  },
                  {
                      "item": {},
                      "cursor": "3"
                  },
                  {
                      "item": {},
                      "cursor": "4"
                  },
                  {
                      "item": {},
                      "cursor": "5"
                  }
              ],
              "totalCount": 123,
              "pageInfo": {
                  "hasPreviousPage": false,
                  "beforeCursor": "1",
                  "hasNextPage": true,
                  "afterCursor": "5"
              }
          }
      }
}

The Public API uses a cursor-based pagination system.

Each set of items, in this example cases, are wrapped in an items field, and each case is defined as an item in that items-field. This means the case is the item.

You can supply an amount variable, to limit the amount of items you get. It defaults to 100.

A cursor is returned for each item. This first and last cursor is also returned in the pageInfo field.

To aid in pagination, you can retrieve a totalCount and both a hasPreviousPage and a hasNextPage, indicating whether pagination in wither direction is possible.

You can move forwards and backwards in the results by supplying a cursor to either the after or before variables.

Variables

The second request wants the next page and sends in the afterCursor as the after variable:

query getCases($amount: Int, $after: String) {
    cases(amount: $amount, after: $after) {
        items {
            item {
                id
                name
            }

            cursor
        }

        totalCount

        pageInfo {
            hasPreviousPage
            beforeCursor
            hasNextPage
            afterCursor
        }
    }
}

variables {
    amount: 5,
    after: "5",
}

This results in the second page being returned:

{
  "data": {
      "cases": {
          "items": [
              {
                  "item": {},
                  "cursor": "6"
              },
              {
                  "item": {},
                  "cursor": "7"
              },
              {
                  "item": {},
                  "cursor": "8"
              },
              {
                  "item": {},
                  "cursor": "9"
              },
              {
                  "item": {},
                  "cursor": "10"
              }
          ],
          "totalCount": 123,
          "pageInfo": {
              "hasPreviousPage": true,
              "beforeCursor": "6",
              "hasNextPage": true,
              "afterCursor": "10"
          }
      }
  }
}
Name type Description
amount Integer The amount of items to return, defaults to 100
after String The after variable gets the amount of items after the supplied cursor.
before String The before variable gets the amount of items before, or behind the supplied cursor.

Errors

The Public API uses the following error codes:

Error Code Meaning
400 Bad Request -- Your request is invalid.
401 Unauthorized -- Your API key is wrong.
403 Forbidden -- The element requested is hidden for administrators only.
404 Not Found -- The specified element could not be found.
405 Method Not Allowed -- You tried to access a element with an invalid method.
406 Not Acceptable -- You requested a format that isn't json.
410 Gone -- The element requested has been removed from our servers.
429 Too Many Requests -- You're requesting too many elements! Slow down!
500 Internal Server Error -- We had a problem with our server. Try again later.
503 Service Unavailable -- We're temporarily offline for maintenance. Please try again later.