DocumentationOverview

API reference

Get from your first API key to a working integration. Validate Kenyan phone numbers, resolve M-Pesa hashes, or decode a CSV.

Base URLhttps://api.dehashit.com/api/v1/

Make your first request

Start with phone validation. It’s free and uses the same authentication as billable lookups.

  1. Create an API key. Open the API keys dashboard and copy your secret. Store it on your server.
  2. Send a validation request. Choose your language, replace the example key, and run the sample.
  3. Read the result. A successful response has status: true and a data object. See a validation response.

No API key yet? Try the lookup tool to explore phone-number hashing.

Choose an endpoint

Integration reference

Authentication, errors, and limits, followed by the complete endpoint reference.

Authentication

Include your API key in the X-Api-Key HTTP header when calling the API from your server. Never expose keys in client-side code, mobile applications, or public version control.

Authentication header

X-Api-Key: dh_live_...

Replace dh_live_... with your own key. The example below validates a phone number and is always free, so you can confirm authentication before making a billable lookup.

Example authenticated request

curl -X POST https://api.dehashit.com/api/v1/validations/phone-validation/ \
  -H "X-Api-Key: dh_live_..." \
  -H "Content-Type: application/json" \
  -d '{"phone_number": "254712345678", "country_code": "254"}'

Manage your keys

Generate, rotate, and revoke keys in the API keys dashboard. Copy a new key when it is created: its secret is shown only once. Rotate a key to replace it, and revoke any key you no longer use. Requests with missing, invalid, or revoked keys return 401 unauthorized. Dashboard web sessions use an HTTP-only cookie. Use API keys for these server integrations; dashboard sessions do not authenticate the decrypt or validation endpoints.

Errors

Successful responses return {"status": true, "status_code": 0, "message": "...", "data": {...}}. Failed responses return status: false, set the HTTP response code to match status_code, and usually provide a machine-readable error_code. Branch on error_code, not on message. Use the HTTP status as a fallback when no error code is provided.

Error codes and recovery

HTTPerror_codeMeaning and next step
400invalid_requestRequest payload failed validation. Inspect errors for field details.
401unauthorizedMissing, invalid, or revoked API key.
402insufficient_balanceMatch found, but wallet balance is below the lookup fee. Number withheld.
403forbiddenBrowser origin or CSRF checks failed, permissions are insufficient, or a service key attempted batch access.
404not_foundNo matching hash, unsupported country, missing resource, or expired batch results. Do not retry unchanged.
405—HTTP method is not supported. This response has no error_code; check the endpoint method.
409idempotency_key_in_progressBatch is still processing or its results are not ready. Wait before checking the same job again.
413payload_too_largeRequest body exceeds 8 KiB, or 2 MiB for a batch upload. Reduce its size.
422idempotency_key_reusedThe retry key was used with another hash or different batch bytes. Use a new key for a new lookup or upload.
429rate_limitedRate limit exceeded. Wait for Retry-After seconds before retrying.
502provider_failedM-Pesa gateway call failed (top-ups only).
503dataset_unavailableLookup is temporarily unavailable. Retry later; never billed.
503dataset_format_legacyLookup is paused for maintenance. Retry later or contact support; never billed.
503lookup_timeoutLookup timed out. Retry; never billed.
503unavailableBilling or database unavailable. Never billed.

Error handling example

curl -i -X POST https://api.dehashit.com/api/v1/msisdn/decrypt/ \
  -H "X-Api-Key: invalid_key" \
  -H "Content-Type: application/json" \
  -d '{"msisdn_hash": "7132104d6aae9c3fac82095a42c2817952bca48e09d98d5bf4ac08218982fb90"}'

Error response

{
  "status": false,
  "status_code": 401,
  "error_code": "unauthorized",
  "message": "Invalid API key.",
  "data": {}
}

This 401 example shows an invalid or revoked key. A missing key returns the same envelope with the message Authentication credentials were not provided.

Unknown JSON fields are rejected (400 invalid_request). An Idempotency-Key longer than 128 characters is also 400 invalid_request. Every response carries X-Request-ID; quote it when contacting support.

Rate limits

Limits use a sliding 60-second window. A 429 response is not billed and includes Retry-After in seconds (normally 60). Wait at least that long before trying again; immediate retries use your retry budget without helping the request succeed.

Request limits

ScopeLimit
Look up hashed MSISDN, per API key100 / min
Look up hashed MSISDN, per source IP400 / min
Phone validation, per API key200 / min
Batch creation, per account10 / min

Service keys cannot access batch jobs; API integrations require a tenant-owned API key.

For billable lookups, preserve the same Idempotency-Key and hash across retries, including after a network timeout. Generate the key once per logical lookup, not inside the retry loop. The Python, Node.js, and Go helpers retry 429 responses only; cURL also retries transient server errors. Handle other API responses in your application.

Handling rate limits (429)

# Modern cURL honors Retry-After for transient HTTP errors.
curl -i -X POST https://api.dehashit.com/api/v1/msisdn/decrypt/ \
  -H "X-Api-Key: dh_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payment-order-123" \
  -d '{"msisdn_hash": "7132104d6aae9c3fac82095a42c2817952bca48e09d98d5bf4ac08218982fb90"}' \
  --retry 3 --retry-max-time 240

Rate limit response (429)

{
  "status": false,
  "status_code": 429,
  "error_code": "rate_limited",
  "message": "Rate limit exceeded. Back off and retry.",
  "data": {}
}

Requests exceeding these thresholds return HTTP 429 with error_code: "rate_limited" and a Retry-After: 60 header. Spread requests over time instead of sending large bursts. Authentication, invalid-input, balance, and key-conflict errors need application action rather than an automatic retry.

Look up hashed MSISDN

POST/api/v1/msisdn/decrypt/

Look up a phone-number hash from an M-Pesa transaction callback. Billable on match only (KES 0.10 per match). Unmatched hashes (404), validation errors (400), rate limits (429), and service outages (503) are free.

Note: The endpoint path keeps the historic /decrypt/ name so existing M-Pesa integrations can adopt it without changes. Submit a supported M-Pesa phone-number hash to retrieve the matching phone number when available.

To safely handle network retries, send an Idempotency-Key header (up to 128 characters). Choose a unique key for each logical lookup, such as your payment ID, and keep that key and hash unchanged for every retry. A successful match is returned again without a second charge; using its key with another hash returns 422 idempotency_key_reused. Failed lookups do not consume the key. Replace the example key payment-order-123 in your integration.

Body parameters

NameTypeRequiredDescription
msisdn_hashstringrequired64-character hexadecimal hash from the M-Pesa callback.

Request

curl -X POST https://api.dehashit.com/api/v1/msisdn/decrypt/ \
  -H "X-Api-Key: dh_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: payment-order-123" \
  -d '{"msisdn_hash": "7132104d6aae9c3fac82095a42c2817952bca48e09d98d5bf4ac08218982fb90"}'

Response

{
  "status": true,
  "status_code": 0,
  "message": "Data found",
  "data": {
    "msisdn": "254712345678",
    "country": "Kenya",
    "carrier": "Safaricom"
  }
}

Validate phone number

POST/api/v1/validations/phone-validation/

Validate a phone number and identify its issuing network operator. Always free. Supports Kenya (country_code: "254"). Non-Kenyan country codes return 404.

Validation checks number format, not whether the number is active or reachable. Carrier is based on the original numbering prefix and does not account for number portability; an unassigned prefix returns carrier: "unknown".

Body parameters

NameTypeRequiredDescription
phone_numberstringrequiredNational or international format: 712345678, 0712345678, 254712345678, +254712345678, or 00254712345678. Spaces, dashes, and brackets are ignored. The response uses the normalized 254 format.
country_codestringrequiredUse the string "254". Other numeric country codes return 404; a nonnumeric code returns 400.

Request

curl -X POST https://api.dehashit.com/api/v1/validations/phone-validation/ \
  -H "X-Api-Key: dh_live_..." -H "Content-Type: application/json" \
  -d '{"phone_number": "712345678", "country_code": "254"}'

Response

{
  "status": true,
  "status_code": 0,
  "message": "Data found.",
  "data": {
    "phone_number": "254712345678",
    "country": "Kenya",
    "carrier": "Safaricom",
    "valid": true,
    "type": "mobile"
  }
}

Batch decode

POST/api/v1/batch/

Decode up to 5,000 phone-number hashes in one CSV upload. New uploads are processed during the request; completed jobs have data.job.status set to done. Each matched row costs KES 0.10; unmatched, invalid, and insufficient-balance rows are free. Duplicate matching rows are each billed separately.

Send X-Batch-Key to make retries safe. Keep it unchanged when retrying the same upload; a completed job is returned without processing or billing again. Reusing the key with different bytes returns 422 idempotency_key_reused; use a new key for a new file. While the same upload is processing, retries return 409 idempotency_key_in_progress.

CSV and headers

FieldRequirement
BodyRaw UTF-8 CSV, up to 2 MiB and 5,000 data rows. Do not send multipart form data.
msisdn_hashRequired column containing a 64-character hexadecimal phone-number hash per row.
Content-Typetext/csv
X-Api-KeyYour API key. All batch job and results operations require authentication.
X-Batch-KeyOptional retry key, up to 128 characters. Recommended for every upload.
X-Batch-FilenameOptional filename for job metadata; stored up to 256 characters.

Save this example as hashes.csv, or start from the CSV template.

hashes.csv

msisdn_hash
7132104d6aae9c3fac82095a42c2817952bca48e09d98d5bf4ac08218982fb90

Request

curl -X POST https://api.dehashit.com/api/v1/batch/ \
  -H "X-Api-Key: dh_live_..." -H "Content-Type: text/csv" \
  -H "X-Batch-Key: campaign-2026-10-01" \
  --data-binary @hashes.csv

Response

{
  "status": true,
  "status_code": 0,
  "message": "Batch ready.",
  "data": {
    "job": {
      "id": 7,
      "status": "done",
      "row_count": 1,
      "completed_rows": 1,
      "matched_count": 1,
      "unmatched_count": 0,
      "invalid_count": 0,
      "charged_cents": 10,
      "filename": "",
      "error": "",
      "created_at": "2026-10-17T09:00:00",
      "finished_at": "2026-10-17T09:00:01"
    }
  }
}

Download results

Use the returned data.job.id to read the job or download its CSV. Results expire after 24 hours; save them promptly. API-key jobs are accessible only with the creating key within its organization. Browser-created jobs are scoped to the creating user and organization, across that user’s login sessions, and are separate from API-key jobs. Rotating a key does not grant the new key access to the old key’s jobs. Requests outside these scopes or for expired results return 404. Results that are not ready return 409.

Method and pathReturns
GET /api/v1/batch/The latest 50 jobs for the calling key or browser actor and organization, as an array in data.
GET /api/v1/batch/{job_id}/A single job in data.job.
GET /api/v1/batch/{job_id}/results/A CSV file, rather than a JSON envelope.

Download a finished job

# Replace 7 with data.job.id from your upload response.
curl "https://api.dehashit.com/api/v1/batch/7/results/" \
  -H "X-Api-Key: dh_live_..." \
  --output batch-7-results.csv

Check each row’s status: match, no_match, invalid, or insufficient_balance. A finished job can include unpaid rows. After topping up, upload only those rows with a new batch key to avoid charging previously matched rows again.

Need a hand with your integration? Contact support.