Skip to main content
Datapoint Analytics

Getting Started

Quickstart: Datapoint in minutes

Start verifying identities and preventing fraud with Datapoint. Get API keys, run your first check and go live in a single afternoon.

Welcome to the Datapoint Documentation. This guide is your starting point to explore and activate Datapoint’s verification and fraud-prevention capabilities. Whether you’re building a fintech app, launching an e-commerce platform, managing compliance or scaling digital operations, our tools help you integrate quickly, cut risk, and ship with confidence.

#Environments

Use the sandbox for sample data and production for live verifications. Paths in this docs site are relative to the base URL you choose (for example /subject/{id}). Do not mix sandbox keys with the production base.

Production

Live verifications. Real subjects, real settlement.

HTTPShttps://api.datapoint.co.ke/graphql
WSSwss://api.datapoint.co.ke/graphql
Keylive_sk_…

Sandbox

Start here

Build and test with seed subjects and simulated settlement. Mirrors production behaviour; no PII submitted here is retained beyond 7 days.

HTTPShttps://sandbox.datapoint.co.ke/graphql
WSSwss://sandbox.datapoint.co.ke/graphql
Keytest_sk_…

Sandbox is safe to break

Submit anything you like. Sandbox data is purged after 7 days and no real settlement is triggered. Production keys reject requests to the sandbox endpoint and vice versa.

#Create API keys

Once your access is approved you’ll receive a secret and publishable key for each environment. Keep secret keys server-side, and rotate them from the client portal.

  1. Open the client portal and switch to the Sandbox workspace.
  2. Create a new API key, name it after the service that will use it.
  3. Copy the secret immediately — we show it once.
  4. Store it in your secret manager (never commit to git).

#Authentication

Datapoint uses bearer tokens on every request. Send your secret in the Authorization header. Keys are scoped per environment and rate-limited independently.

HeaderAuthorization: Bearer test_sk_live_demo_000

#Make your first request

The example below submits a lightweight identity verification against the sandbox. Swap in your sandbox key, run it, and inspect the response.

curl https://sandbox.datapoint.co.ke/graphql \
  -H "Authorization: Bearer test_sk_live_demo_000" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation($input: VerificationRequestInput!) { requestVerification(input: $input) { success data { id status } } }",
    "variables": {
      "input": {
        "requester": { "name": "Savanna Digital Ltd", "email": "api@savannadigital.co.ke" },
        "scope": { "checks": ["IDENTITY"], "purpose": "PRE_EMPLOYMENT" },
        "subject": { "firstName": "Jane", "lastName": "Mwangi", "idNumber": "12345678" }
      }
    }
  }'

Expect this response

{"data":{"requestVerification":{"success":true,"message":"Verification request accepted"}}}

Sandbox·no real data submitted
▸ Click Run in sandbox to fire a live request against the mock API

#Identity Hub

A composable identity verification surface built to fit your market and use case. Whether you’re verifying individuals (KYC) or businesses (KYB), the Identity Hub supports:

  • Government-issued ID verification and document analysis.
  • Biometric verification (facial match, liveness detection).
  • Address verification (digital and field-based).
  • Phone and email ownership proofs.

#ID & Passport Checks

Submit a national ID number, alien ID, or passport number. Datapoint cross-references the relevant register (IPRS for national IDs, ECITIZEN for passports) and returns a name match, date of birth, gender, and citizenship status.

mutation InstantCheck($input: InstantCheckRequestInput!) {
  instantCheckRequest(input: $input) {
    success
    message
    data { id }
  }
}

# variables — ID check
{
  "input": {
    "personalInfo": { "firstName": "Jane", "lastName": "Mwangi",
                      "phone": "254712345678", "email": "j.mwangi@gmail.com" },
    "identificationInfo": { "idNumber": "12345678", "idFront": null, "idBack": null },
    "selectedChecks": ["IDENTITY"],
    "consent": true
  }
}

Sandbox ID numbers

12345678 → PASS (Jane Mwangi) | 28944075 → PASS (vehicle check included) | 99999999 → FAIL | 00000000 → ESCALATED

#Biometric Verification

Biometric verification confirms that the person presenting an ID is its genuine holder. Create a Didit session, open the session URL in an embedded web view or redirect, and poll or subscribe for the result. Datapoint stores only the session outcome — not the raw biometric data.

mutation CreateSession(
  $firstName: String, $lastName: String,
  $email: String,    $phone: String,
  $documentNumber: String
) {
  createDiditSession(
    firstName: $firstName, lastName: $lastName,
    email: $email, phone: $phone,
    documentNumber: $documentNumber
  ) {
    diditSessionId
    sessionToken
    url          # redirect user here
    status
  }
}

After the user completes the flow, Datapoint fires a verification.completed webhook and attaches the liveness score and face-match confidence to the certificate.

#Document Verification

Compare an ID photograph against a live selfie to confirm the document belongs to the person presenting it. Upload both files as multipart form fields; the response includes a similarity confidence score and a pass/fail decision.

mutation CompareIdPhoto($idPhoto: Upload!, $selfie: Upload!) {
  compareIdPhoto(idPhoto: $idPhoto, selfie: $selfie) {
    isIdentical
    confidence
    faceRectangle { top left width height }
  }
}

Expect this response

{ "compareIdPhoto": { "isIdentical": true, "confidence": 0.962, "faceRectangle": { "top": 142, "left": 118, "width": 224, "height": 296 } } }

#Liveness Capture

Passive liveness runs during the biometric session. No explicit action is required from the user — the SDK analyses motion and depth cues automatically. After the session closes, call refreshDiditSession to retrieve the liveness score and whether the check passed.

mutation RefreshSession($diditSessionId: String!) {
  refreshDiditSession(diditSessionId: $diditSessionId) {
    status
    terminal
    outcome
    livenessScore
    livenessPassed
    faceMatchConfidence
    holderName
    documentNumber
    documentExpiresAt
    warnings
  }
}

#Initiate Verification

Use requestVerification to submit a B2B verification request on behalf of an organisation. Specify the scope (which checks to run), the subject, and a requester contact. Datapoint acknowledges the request and begins processing asynchronously.

mutation RequestVerification($input: VerificationRequestInput!) {
  requestVerification(input: $input) {
    success
    message
  }
}

# variables
{
  "input": {
    "requester": {
      "organisation": "Savanna Digital Ltd",
      "contactPerson": "Wanjiku Maina",
      "email": "api@savannadigital.co.ke",
      "telephone": "+254712000000"
    },
    "verification": {
      "category": "KYC",
      "requiredTiming": "STANDARD",
      "subjectVolume": "1-10",
      "scope": {
        "mode": "recommended",
        "selectedChecks": ["IDENTITY", "CRIMINAL_RECORD"],
        "optionalChecks": null,
        "preset": null,
        "customScope": null
      },
      "additionalDetails": null,
      "decisionSupported": null
    },
    "authorityConfirmed": true,
    "website": "https://savannadigital.co.ke"
  }
}

#Check Status

Poll the status of an in-progress verification by its certificate number. The status field moves through PENDING → GENERATING → READY (or FAILED). Subscribe to the certificate event instead if you want a push-based update.

query GetCertificate($certNo: String!) {
  certificateById(certNo: $certNo) {
    certNo
    status        # PENDING | GENERATING | READY | FAILED
    finalOutcome  # PASS | FAIL | EXPIRED
    issuedAt
    clientName
    verificationType
  }
}

# variables
{ "certNo": "DP-SANDBOX-00001" }

#Verification Reports

A completed verification produces a certificate. Fetch it with certificateById to retrieve the full check breakdown. Each check carries an extras field with the raw source-API payload (IPRS, NTSA, DCI, BRS, etc.).

query VerificationReport($certNo: String!) {
  certificateById(certNo: $certNo) {
    certNo
    clientName
    clientEmail
    clientPhone
    verificationType
    finalOutcome
    issuedAt
    expiresAt
    status
    checks {
      name
      result        # PASS | FAIL
      documentNo
      extras        # raw source-API payload
      createdAt
    }
  }
}

extras field contains raw third-party data

The shape of extras varies by check type: NTSA returns vehicle and owner objects; IPRS returns identity fields; DCI returns certificate metadata. Treat extras as untyped JSON in your client.

#Webhook signatures

Every webhook delivery includes an X-Datapoint-Signature header. Compute HMAC-SHA256 of the raw request body using your webhook secret and compare with constant-time equality.

import crypto from "node:crypto";

export function verifySignature(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex"),
  );
}

#Webhook events

Datapoint fires the following events to your registered webhook URL. Each delivery includes an X-Datapoint-Signature header for verification.

EventTrigger
verification.completedCheck transitions to PASSED, FAILED or ESCALATED
verification.escalatedDiscrepancy detected — field assignment or re-upload triggered
verification.flag_raisedA specific check is flagged for manual review
document.expiringA tracked document is within 30 days of expiry
{
  "event": "verification.completed",
  "verification_id": "DP-2026-08921",
  "status": "PASSED",
  "subject_ref": "EMP-9021",
  "timestamp": "2026-10-12T09:55:00Z",
  "summary": {
    "iprs_check": "MATCHED",
    "academic_check": "VERIFIED",
    "good_conduct": "CLEAR"
  },
  "report_url": "https://dpaverify.com/reports/DP-2026-08921.pdf"
}

#Subscriptions

Subscribe over WebSocket to receive real-time events without polling. Use the wss:// endpoint and the graphql-ws protocol. Two subscriptions are available: certificate for verification completion events and mpesaPayment for settlement confirmation.

subscription CertificateReady($verificationRequestId: String!) {
  certificate(verificationRequestId: $verificationRequestId) {
    status
    state   # AWAITING_IDENTITY | GENERATING | READY | FAILED
    message
    data {
      fileUrl
      verificationRequestId
    }
  }
}

#AML / PEP Screening

Beta

Screen individuals and entities against global AML and PEP (Politically Exposed Persons) watchlists. Returns a risk score, match status, and the list sources that triggered a hit. Available to approved integrations — contact us to enable this capability on your account.

Beta capability

AML / PEP screening is in closed beta. The API shape is stable but subject to minor changes before general availability. Reach out to request access.

#Risk Scoring

Coming Soon

Composite risk scores that combine identity, criminal, financial, and behavioural signals into a single decision-ready score. Risk Scoring will be available later in 2026. Sign up for early access via the client portal.

#Kenya Business Lookup

Look up a registered Kenyan business by name, registration number, or KRA PIN. Returns the company record from the Business Registration Service (BRS), including directors, shareholders, and share allocations.

mutation BusinessCheck($input: InstantCheckRequestInput!) {
  instantCheckRequest(input: $input) {
    success
    message
    data { id }
  }
}

# variables — include BRS_LOOKUP in selectedChecks
{
  "input": {
    "personalInfo": { "firstName": "Gavin", "lastName": "Castro",
                      "phone": "+254722984594", "email": "romeogavin@yahoo.com" },
    "identificationInfo": { "idNumber": "24958338", "idFront": null, "idBack": null },
    "selectedChecks": ["IDENTITY", "BRS_LOOKUP"],
    "consent": true
  }
}

#KRA PIN Verification

Verify a Kenya Revenue Authority PIN against the KRA register. Confirms the PIN is active and returns the registered name and taxpayer category. Include KRA_PIN in selectedChecks on any instant check request.

mutation KraCheck($input: InstantCheckRequestInput!) {
  instantCheckRequest(input: $input) {
    success
    message
    data { id }
  }
}

# variables
{
  "input": {
    "personalInfo": { "firstName": "Jane", "lastName": "Mwangi",
                      "phone": "254712345678", "email": "j.mwangi@gmail.com" },
    "identificationInfo": { "idNumber": "12345678", "idFront": null, "idBack": null },
    "selectedChecks": ["IDENTITY", "KRA_PIN"],
    "consent": true
  }
}

#M-Pesa STK Push

Initiate a Safaricom M-Pesa STK push to collect payment before releasing a verification. The mutation returns a merchantRequestID you can use to subscribe to the settlement event.

mutation StkPush($phoneNumber: String!) {
  initiateStkPush(phoneNumber: $phoneNumber) {
    success
    message
    data {
      merchantRequestID
      verificationRequestId
    }
  }
}

# variables
{ "phoneNumber": "254712345678" }

Sandbox behaviour

STK push always returns success in the sandbox. No real money is moved. Use merchantRequestID "sandbox-mrq-00000001" in the mpesaPayment subscription to receive a simulated SUCCESS event.

#Payment Status

Payment status moves through PENDING → SUCCESS (or FAILED) after the customer completes the STK prompt. Subscribe to mpesaPayment for a real-time push, or poll certificateById and inspect the payment field.

query CertWithPayment($certNo: String!) {
  certificateById(certNo: $certNo) {
    certNo
    status
    payment {
      status          # PENDING | SUCCESS | FAILED
      receiptNumber
      amount
      paidAt
    }
  }
}

#Settlement Events

Subscribe to mpesaPayment to receive a push notification the moment M-Pesa settles. The event carries the receipt number, amount, and final status. Use the receipt number for reconciliation.

subscription Settlement($merchantRequestID: String!) {
  mpesaPayment(merchantRequestID: $merchantRequestID) {
    status
    resultDesc
    receiptNumber
    amount
    merchantRequestID
  }
}

#Rate limits

Default limits are 60 requests per minute per key in sandbox and 240 per minute in production. Every response includes X-RateLimit-Remaining and X-RateLimit-Reset headers. Burst allowances and higher caps are available on request.

#Errors

All errors follow GraphQL convention — HTTP 200 with an errors array. HTTP 4xx is only returned for transport-level issues (missing auth, malformed JSON).

CodeHTTPMeaning
UNAUTHORIZED401Missing or invalid API key
FORBIDDEN403Key does not have permission for this operation
NOT_FOUND200Resource (certificate, session) not found
VALIDATION_ERROR200Input failed schema validation — check extensions.fields
RATE_LIMITED429Request rate exceeded — retry after X-RateLimit-Reset
INTERNAL_ERROR200Unexpected server error — contact support with the request ID
{
  "errors": [{
    "message": "Unauthorized: missing or invalid API key",
    "extensions": {
      "code": "UNAUTHORIZED"
    }
  }]
}

#Changelog

Breaking changes are versioned. Non-breaking additions (new fields, new optional inputs) are released without a version bump.

2026-10-15

Sandbox GA

  • Sandbox API available at /api/sandbox/graphql with Bearer auth.
  • Postman collection published — download from this page.
  • Scenario-based responses: ID 12345678 PASS, 99999999 FAIL, 00000000 ESCALATED.

2026-09-01

Subscriptions

  • certificate and mpesaPayment WebSocket subscriptions available on wss:// endpoint.
  • graphql-ws protocol replaces subscriptions-transport-ws.

2026-07-14

Instant Checks v2

  • instantCheckRequest replaces clientValidation for new integrations.
  • Alien ID and passport checks added to selectedChecks.
  • BRS_LOOKUP and KRA_PIN added as check types.