Skip to content
Documentation

API reference

The programmatic surface: base URL, authentication, the send endpoint, public pricing, the response envelope, and errors.

Darkel’s public API is intentionally small. Your backend sends messages and can read live pricing; everything else (connecting a number, templates, top-ups, team) is done in the dashboard. This page is the precise contract for the endpoints you call from code.

Base URL & versioning

text
https://api.darkel.ng
  • One base URL for sandbox and live — the API key decides the environment (dk_test_ vs dk_live_).
  • Versioned endpoints are prefixed with /v1. Treat the version as part of the path.
  • All requests and responses are JSON. Send Content-Type: application/json on requests with a body.

Authentication

Send your API key in the X-Api-Key header. The key identifies the workspace; there is no separate account ID to send. Full details in Authentication.

text
X-Api-Key: dk_live_YOUR_API_KEY

Response envelope

Successful responses are wrapped in a standard envelope. Your data is always under data.

Envelope shape
{
  "success": true,
  "statusCode": 201,
  "message": "Request successful",
  "data": { /* the resource — e.g. the message record */ }
}

Errors use the same envelope idea with success: false and a human-readable message:

Error shape
{
  "success": false,
  "statusCode": 400,
  "message": "to must be a valid E.164 phone number"
}

Send a message

POST/v1/whatsapp/messagesX-Api-Key

Send an approved template message from the key’s workspace.

Request body

FieldTypeRequiredDescription
tostringYesRecipient in E.164, e.g. +2348012345678
templateobjectYesThe template to send (below)
template.namestringYesExact name of an approved template
template.languageCodestringNoDefaults to en_US; must match the template
template.componentsarrayConditionalVariable values; required when the template has placeholders
Send a template message
const res = await fetch("https://api.darkel.ng/v1/whatsapp/messages", {
  method: "POST",
  headers: {
    "X-Api-Key": process.env.DARKEL_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    to: "+2348012345678",
    template: {
      name: "order_confirmation",
      languageCode: "en_US",
      components: [
        { type: "body", parameters: [{ type: "text", text: "Ada" }] },
      ],
    },
  }),
});

const { data } = await res.json();
if (!res.ok) throw new Error("Send failed");
console.log("Queued:", data.id);
Response (201) — message record under data
{
  "success": true,
  "statusCode": 201,
  "message": "Request successful",
  "data": {
    "id": "665f1c0b8a3e2d0012a4b9f1",
    "to": "+2348012345678",
    "direction": "outbound",
    "templateName": "order_confirmation",
    "status": "pending",
    "pricing": {
      "country": "NG",
      "expectedCategory": "marketing",
      "chargedKobo": 7998
    },
    "createdAt": "2026-06-27T09:30:00.000Z"
  }
}

The response is returned the moment the message is accepted, so status is usually pending or sent. Track the final outcome in the dashboard — see Delivery & errors.

The pricing object

A message sent on a shared Darkel number carries a pricing object. A message sent on your own number is not charged by Darkel. The price is Meta’s price for the message type and recipient country, in naira — see the price list.

FieldDescription
pricing.countryRecipient country, from the country calling code
pricing.expectedCategoryThe message type the price was calculated for at send time
pricing.chargedKoboAmount currently held for the message, in kobo
pricing.billedCategoryOnce Meta confirms: the type Meta actually billed
pricing.finalKoboOnce Meta confirms: the final amount for the message, in kobo

Components by template type

  • No variables — omit components entirely.
  • Body variables — one { "type": "body", "parameters": [...] } entry; each parameter fills {{1}}, {{2}} … in order.
  • Authentication (OTP) — a single body parameter carrying the code, e.g. [{ "type": "body", "parameters": [{ "type": "text", "text": "483920" }] }].

Get product pricing

GET/products/:keyNone (public)

Read live pricing for an active product. No key required.

Request
curl https://api.darkel.ng/products/whatsapp
Response (200) — values are placeholders, not real prices
{
  "success": true,
  "statusCode": 200,
  "message": "Request successful",
  "data": {
    "key": "<product-key>",
    "name": "<product name>",
    "description": "<short description>",
    "monthlyFee": <monthly access fee in kobo>,
    "yearlyFee": <yearly access fee in kobo>
  }
}

Amounts are in kobo (₦1 = 100 kobo) — read the live values from this endpoint rather than hard-coding them. Returns 404 if the product key does not exist or is inactive. More in Billing & pricing.

Get WhatsApp message prices

GET/whatsapp/pricesNone (public)

Read the current per-message price list. No key required.

Request
curl https://api.darkel.ng/whatsapp/prices
Response (200) — values are placeholders, not real prices
{
  "success": true,
  "statusCode": 200,
  "message": "Request successful",
  "data": {
    "koboPerUsd": <naira per US dollar, in kobo, or null>,
    "updatedAt": "<ISO timestamp, or null>",
    "countries": [
      {
        "country": "NG",
        "name": "Nigeria",
        "prices": { "marketing": <kobo>, "utility": <kobo>, "...": <kobo> }
      }
    ]
  }
}

Amounts are in kobo. countries is empty while pricing is not configured. A message is charged at the price for its type and the recipient’s country, converted at the published rate. More in Billing & pricing.

Status codes

CodeMeaning
200 / 201Success
400Validation or business-rule error (bad number, sandbox recipient, low balance, not set up)
401Missing, invalid or revoked API key
403WhatsApp is not enabled for the workspace
404Resource not found (e.g. unknown product key)
429Too many requests — slow down and retry

For Meta-side failures that occur after a message is accepted, see the error-code table in Delivery & errors.