Everything a backend needs.

Verify a payment receipt with one POST, by reference or from a screenshot. All responses are JSON.

Authentication

Every request needs an API key created from your dashboard. Send it as a bearer token:

Authorization: Bearer mela_live_xxxxxxxxxxxxxxxxxxxx

Keys have a configurable daily request limit. Exceeding it returns HTTP 429. mela_test_ keys skip real bank calls and return deterministic simulated receipts (marked simulated: true) so you can build without spending live quota.

Keep keys on the server

A Mela key is a backend credential. Call the API from your product's server (Next.js route handlers, workers, edge functions), never from the browser, a mobile app binary, or any NEXT_PUBLIC_* env var. The secret is shown once at creation and stored as a hash; if it leaks, revoke it and mint a new one.

# .env.local  (server only, not committed, not NEXT_PUBLIC_)
MELA_API_URL=https://verifypayment.tec.et
MELA_API_KEY=mela_live_xxxxxxxxxxxxxxxxxxxx

# Node / Next.js server route
const res = await fetch(`${process.env.MELA_API_URL}/api/v1/verify`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MELA_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ bank: "boa", reference }),
});

The frontend only uploads a slip or a reference to your backend. Your backend then talks to Mela with the key.

One key per project

If you run several products against this Mela server, create a project and a key for each. Usage, limits, and revocation stay isolated.

Create projects and keys from the dashboard. Paste each printed secret into that project's server env as MELA_API_KEY.

POST /api/v1/verify

Verify a receipt by its transaction reference.

POST https://verifypayment.tec.et/api/v1/verify
Content-Type: application/json
Authorization: Bearer mela_live_...

{
  "bank": "cbe",              // required, see /banks for codes
  "reference": "FT24123ABCDE",// required
  "account_suffix": "12345",  // required for banks that need it (see /banks)
  "phone": "0911223344",      // required for banks that need it
  "expected_amount": 1500     // optional: flags a mismatch, doesn't hard-fail
}

Response on success (HTTP 200):

{
  "verified": true,
  "bank": "cbe",
  "reference": "FT24123ABCDE",
  "receipt": {
    "payer_name": "Abebe Kebede",
    "receiver_name": "Kaleab Store",
    "amount": 1500,
    "currency": "ETB",
    "date": "2026-08-10"
  },
  "amount_matches": true
}

Response on failure (HTTP 404/400/429):

{
  "verified": false,
  "bank": "cbe",
  "reference": "FT24123ABCDE",
  "error": "No receipt found for this reference",
  "error_code": "not_found"
}

Possible error_code values: unknown_bank, invalid_reference, not_found, bank_disabled, relay_not_configured, upstream_error, parse_error.

POST /api/v1/verify/batch

Verify up to 50 references in one call. Each item uses the same fields as the single verify endpoint. The response contains a per-item result array plus a summary.

POST https://verifypayment.tec.et/api/v1/verify/batch
Content-Type: application/json
Authorization: Bearer mela_live_...

{
  "items": [
    { "bank": "cbe", "reference": "FT24123ABCDE", "account_suffix": "12345" },
    { "bank": "telebirr", "reference": "TB2612345" }
  ]
}

Response (HTTP 200, even when some items fail):

{
  "summary": { "total": 2, "verified": 1, "failed": 1 },
  "results": [
    { "verified": true,  "bank": "cbe",      "reference": "FT24123ABCDE", "...": "..." },
    { "verified": false, "bank": "telebirr", "reference": "TB2612345",
      "error": "No receipt found for this reference", "error_code": "not_found" }
  ],
  "response_ms": 812
}

POST /api/v1/verify/image

Verify from a receipt screenshot instead of typing the reference. Mela first tries to decode a QR code in the image; if there isn't one, it falls back to OCR.

POST https://verifypayment.tec.et/api/v1/verify/image
Content-Type: multipart/form-data
Authorization: Bearer mela_live_...

image: <file>              // required
bank: cbe                  // optional: helps when OCR/QR can't tell which bank
expected_amount: 1500      // optional

The response is the same shape as /verify, plus a `detected` object showing what was read from the image.

GET /api/v1/banks

No authentication required. Lists every bank Mela currently supports and their requirements.

curl https://verifypayment.tec.et/api/v1/banks

Webhooks

Configure one HTTPS endpoint per account from the dashboard. Every completed verification for any of your keys is POSTed to it as JSON with event type verification.completed.

Verify authenticity with the Mela-Signature header: t=<unix ts>,v1=<hmac_sha256> where the HMAC is computed over the string "<timestamp>.<raw body>" using your signing secret. Failed deliveries are retried up to 3 times.

Rate limits & errors

Each API key has a daily request limit set when it's created (default 2,000/day). Requests over the limit get HTTP 429. Revoked keys get HTTP 401 immediately.

A public health view of every bank is available at /status and as JSON at GET /api/status.