imeifixer
Menu

Reseller API

Connect a Dhru Fusion panel, or call the REST API from your own code. Both use the same API key and charge your wallet at your reseller prices. Keys are for reseller accounts and each one works from a single server IP.

What you can order

This store sells IMEI and server services through the API. The same API key works for every kind; a kind the store doesn't sell is refused and its list is empty.

Service kinds and the calls that list and place them
KindList the servicesPlace an order
IMEIDhru imeiservicelist; REST GET /services → imeiplaceimeiorder; POST /orders
ServerDhru serverservicelist; REST GET /services → serverplaceserverorder (or placeimeiorder); POST /orders

Every rate limit, order cap and timeout this store enforces is on the Limits page.

API flavour

Quickstart

Add imeifixer to your Dhru Fusion panel as an API supplier (type: Dhru Fusion). You need three values:

  1. 1

    API URL

    https://imeifixer.com/api/index.php
  2. 2

    Username

    The email address you sign in to imeifixer with.

  3. 3

    API key

    Create one under Account → API keys, bound to the public IP of the server your panel runs on. API keys need a reseller account.

Then use your panel's connection test or service sync: it calls accountinfo and imeiservicelist (and serverservicelist for server services).

Endpoint

The gateway answers on any of these URLs; they are the same endpoint, so use whichever your panel expects:

  • https://imeifixer.com/api/index.php
  • https://imeifixer.com/api/v1/services
  • https://imeifixer.com/api/v1/services/api/index.php

Requests are POST, form-encoded, with the fields username, apiaccesskey, action and, for actions that take input, parameters: the base64 of a JSON object (base64 XML <PARAMETERS>…</PARAMETERS> is accepted too). For single-order actions, top-level ID, IMEI and Qnt fields fill in anything parameters leaves out. A GET answers 405.

Actions

Dhru actions
actionparametersReturns in data
accountinfononecredit, mail, currency
imeiservicelistnoneServices grouped by GROUPNAME: SERVICEID, SERVICENAME, CREDIT (your price), TIME, QNT, Requires (extra fields)
serverservicelistnoneServer services and remote services (SERVICETYPE REMOTE), in the same GROUPNAME / SERVICES shape as imeiservicelist
placeimeiorderID, IMEI, Qnt, plus any service fieldsREFERENCEID, MESSAGE
placeimeiorderbulk{ your ref: { ID, IMEI, Qnt, … } }, up to 200 linesPer line SUCCESS or ERROR; each line is charged on its own
getimeiorderID = the REFERENCEIDSTATUS, CODE
getimeiorderbulk{ your ref: REFERENCEID }Per line STATUS and CODE, or ERROR
placeserverorder, placeserverorderbulkas the IMEI versions; IMEI carries the server value (username, email, …)as the IMEI versions
getserverorder, getserverorderbulkas the IMEI versionsas the IMEI versions

Unknown fields in a placement are ignored, so panel extras do no harm.

Server and remote services work like IMEI ones: sync them with serverservicelist, place them with placeserverorder (or placeimeiorder; both accept any SERVICEID) and poll with getserverorder. Put the value the service needs (a username, an email, a serial) in IMEI; if you leave IMEI out, the service's first field from Requires is used. Dhru has no list action for remote services, so serverservicelist lists them too, tagged SERVICETYPE REMOTE (see Service types). A service's extra fields go in CUSTOMFIELD (see Services with extra fields).

First call: accountinfo

Request
curl https://imeifixer.com/api/index.php \
  --data-urlencode username=you@example.com \
  --data-urlencode apiaccesskey=imf_your_key \
  --data-urlencode action=accountinfo
Response
{
  "SUCCESS": [
    {
      "message": "",
      "data": {
        "credit": "20.0000",
        "mail": "you@example.com",
        "currency": "USD"
      }
    }
  ],
  "apiversion": "6.1"
}

Run it from the server whose IP the key is bound to. From anywhere else you get the invalid credentials error.

Place an order

Build parameters by base64-encoding the JSON. On Linux, add -w0 to base64 for long values so it doesn't wrap lines.

Request
PARAMS=$(printf '%s' '{"ID":"12","IMEI":"354800000012950","Qnt":1}' | base64)

curl https://imeifixer.com/api/index.php \
  --data-urlencode username=you@example.com \
  --data-urlencode apiaccesskey=imf_your_key \
  --data-urlencode action=placeimeiorder \
  --data-urlencode "parameters=$PARAMS"

PARAMS comes out as eyJJRCI6IjEyIiwiSU1FSSI6IjM1NDgwMDAwMDAxMjk1MCIsIlFudCI6MX0=. Always send it with --data-urlencode (or your HTTP library's form encoding): base64 contains + and /.

Response
{
  "SUCCESS": [
    {
      "message": "",
      "data": {
        "REFERENCEID": "IF1",
        "MESSAGE": "Order placed."
      }
    }
  ],
  "apiversion": "6.1"
}

Store REFERENCEID; it is how you ask for the result. The order is charged when it is placed.

Get the result

Request
PARAMS=$(printf '%s' '{"ID":"IF1"}' | base64)

curl https://imeifixer.com/api/index.php \
  --data-urlencode username=you@example.com \
  --data-urlencode apiaccesskey=imf_your_key \
  --data-urlencode action=getimeiorder \
  --data-urlencode "parameters=$PARAMS"
Response while in process
{
  "SUCCESS": [
    {
      "message": "",
      "data": {
        "STATUS": "1",
        "CODE": ""
      }
    }
  ],
  "apiversion": "6.1"
}
Response when done
{
  "SUCCESS": [
    {
      "message": "",
      "data": {
        "STATUS": "4",
        "CODE": "Model: iPhone 13\nIMEI: 354800000012950\nFind My: OFF\nBlacklist: Clean"
      }
    }
  ],
  "apiversion": "6.1"
}

We check with the supplier about every 60 seconds, so polling an order more often than that only uses up your limit.

STATUS values

STATUS values
STATUSMeaningDetails
0NewPaid and queued for the supplier.
1In processAccepted by the supplier; we keep checking it.
3RejectedRejected, or refunded because it could not be done. The charge is back in your balance automatically.
4SuccessDone. The result is in CODE.

These are the Dhru Fusion codes, so a stock panel maps them without changes: 4 is success and 3 is rejected. If your panel was set up with the two swapped, put them back to the Dhru default.

Bulk

placeimeiorderbulk takes an object keyed by your own line references (up to 200 lines). Each line is checked and charged on its own, so one bad line doesn't stop the others.

parameters (before base64)
{
  "a1": {
    "ID": "12",
    "IMEI": "354800000012950",
    "Qnt": 1
  },
  "a2": {
    "ID": "12",
    "IMEI": "35480000001"
  }
}
data in the SUCCESS envelope
{
  "a1": {
    "SUCCESS": [
      {
        "REFERENCEID": "IF2",
        "MESSAGE": "Order placed."
      }
    ]
  },
  "a2": {
    "ERROR": [
      {
        "message": "invalid IMEI"
      }
    ]
  }
}

getimeiorderbulk works the same way: send {"a1": "IF2", …} and get STATUS and CODE per reference.

Responses

Every response is HTTP 200 with "apiversion": "6.1". Check for a SUCCESS or an ERROR key, not the HTTP status.

SUCCESS
{
  "SUCCESS": [
    {
      "message": "",
      "data": {
        "REFERENCEID": "IF1",
        "MESSAGE": "Order placed."
      }
    }
  ],
  "apiversion": "6.1"
}
ERROR
{
  "ERROR": [
    {
      "message": "Invalid credentials: check username (your login email), API key, and that this server's IP matches the key's bound IP."
    }
  ],
  "apiversion": "6.1"
}

IP binding and lockout

  • Each key works only from the IP you bound it to. Behind NAT or a proxy that is your server's public (outgoing) address. To move servers, create a key for the new IP and revoke the old one. Don't know the IP? Create a key that binds to the first server that calls.
  • After 10 failed checks (wrong username, key or IP) from one IP within 15 minutes, that IP is refused, even with the right key, until the time runs out. The error says how many minutes are left.
  • Status calls (getimeiorder, getserverorder and their bulk versions) are limited to 60 per 60 seconds per login and IP. Use the bulk version to check many orders at once.
  • Recent calls on each key shows what we received and what we answered. A call that fails the credential check can't be tied to a key, so it isn't listed there.

Every limit in one place: the Limits page.

Callbacks (push instead of polling)

Set a callback URL on an API key in API keys and we POST each order placed with that key to it as soon as the order is completed, rejected or refunded. You can keep polling as a fallback; the callback just tells you sooner.

  • The URL must be https:// on the standard port, with a public host name. We don't follow redirects.
  • Answer with any 2xx within 10 seconds. Anything else, or no answer, is retried after 1 min, 5 min, 30 min, 2 h and 6 h; then we give up. The key's page lists the last 20 deliveries and what your server answered.
  • After we give up, the delivery shows Gave up on the key's page. Nothing is lost: getimeiorder (or GET /orders/<reference_id> on REST) still returns the result, so poll it. Once your endpoint works again, ask support to resend the callback and quote the REFERENCEID.
  • A delivery can arrive twice (for example after a timeout on your side). Use reference_id (or the X-Imeifixer-Delivery header) to ignore repeats.
  • status is the Dhru STATUS value: 4 completed, 3 rejected or refunded. code is only present for a completed order.
Request headers
POST /your/callback/path HTTP/1.1
Content-Type: application/json
User-Agent: imeifixer-callbacks/1
X-Imeifixer-Delivery: 8812
X-Imeifixer-Signature: t=1791109267,v1=5f1c0b0e…(64 hex characters)
Request body
{
  "event": "order.finished",
  "reference_id": "IF10452",
  "status": "4",
  "status_text": "completed",
  "code": "Model: iPhone 13\nIMEI: 354800000012950\nFind My: OFF",
  "completed_at": "2026-10-04T10:21:07.512918+00:00"
}
Callback fields
FieldMeaning
eventAlways order.finished (a test from the key page sends ping).
reference_idThe REFERENCEID you got when you placed the order.
statusDhru STATUS: 4 completed, 3 rejected/refunded.
status_textcompleted, rejected or refunded.
codeThe result (unlock code, check report). Completed orders only.
completed_atWhen the order finished (ISO 8601, UTC).

Check the signature before trusting the body. X-Imeifixer-Signature is t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of <t>.<raw body> keyed with the key's signing secret. Use the raw bytes you received, not re-encoded JSON, and refuse timestamps more than 5 minutes old.

Python
import hashlib, hmac, time

SECRET = "whsec_..."  # shown once when you set the callback URL

def verify(raw_body: bytes, header: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    timestamp = int(parts.get("t", "0"))
    if abs(time.time() - timestamp) > tolerance:
        return False  # too old: a replay
    signed = f"{timestamp}.".encode() + raw_body
    expected = hmac.new(SECRET.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))
Node.js
import crypto from "node:crypto";

const SECRET = process.env.IMEIFIXER_CALLBACK_SECRET; // "whsec_..."

export function verify(rawBody /* Buffer */, header, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
  const timestamp = Number(parts.t);
  if (!timestamp || Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(`${timestamp}.`)
    .update(rawBody)
    .digest("hex");
  const given = Buffer.from(parts.v1 ?? "", "utf8");
  return given.length === expected.length && crypto.timingSafeEqual(given, Buffer.from(expected));
}
PHP
<?php
$secret = 'whsec_...';
$raw = file_get_contents('php://input');           // the raw body, before json_decode
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_IMEIFIXER_SIGNATURE'] ?? ''), $sig);
$ok = isset($sig['t'], $sig['v1'])
    && abs(time() - (int) $sig['t']) <= 300
    && hash_equals(hash_hmac('sha256', $sig['t'] . '.' . $raw, $secret), $sig['v1']);
if (!$ok) { http_response_code(400); exit; }
$event = json_decode($raw, true);
http_response_code(200);

Error messages

ERROR messages
messageWhat it means
Invalid credentials: check username (your login email), API key, and that this server's IP matches the key's bound IP.Username, key or calling IP is wrong. We don't say which, on purpose.
Too many failed attempts from this IP; try again in N minutes.This IP is locked out after repeated failures (see above). Fix the credentials, then wait.
rate limit exceeded — slow downMore than 60 status calls in 60 s from this login and IP.
unknown action: X. Valid actions: …The action name is misspelt; the message lists them all.
parameters is not valid base64 / did not decode to an object / is not valid XMLCheck the parameters recipe below.
missing or invalid service IDID is missing or isn't one of the SERVICEIDs in imeiservicelist or serverservicelist.
service not found or disabledThe service is switched off.
missing IMEI / invalid IMEI / invalid serial numberSend one 15-digit IMEI (or serial) per order.
missing value / invalid valueServer and remote services: send the value in IMEI (or the service's first field), 1-64 printable characters.
invalid quantity / Minimum quantity is N. / Maximum quantity is N.Qnt is outside the service's range.
<field>: <problem>A required service field (see Requires) is missing or invalid.
insufficient balanceTop up your wallet. Nothing was charged.
service temporarily unavailableThe service can't be sold right now. Try later.
at most 200 orders per requestSplit the bulk call.
missing order ID / unknown orderSend the REFERENCEID we returned when you placed it.
temporarily unavailable, try again (ref …)A problem on our side. Retry in a minute; quote the ref if you contact support.

Services with extra fields (CUSTOMFIELD)

Some services need more than the IMEI: a carrier, a model, a username. Each service-list row names them in Requires and in Requires.Custom, one entry per field with fieldname (the field key), fieldtype, label, required and, for a choice, fieldoptions. Every product page shows the same keys under Order by API.

Dhru: put CUSTOMFIELD inside parameters: the base64 of a JSON object keyed by field key (plain JSON is accepted too), at most 8 KB, each value text or a number. A key matches the field key in any case, or the field's label. A key sent at the top level of parameters wins over the same key in CUSTOMFIELD.

CUSTOMFIELD (before base64)
{
  "carrier": "AT&T",
  "model": "iPhone 13"
}
parameters (before base64)
{
  "ID": "12",
  "IMEI": "354800000012950",
  "Qnt": 1,
  "CUSTOMFIELD": "eyJjYXJyaWVyIjoiQVQmVCIsIm1vZGVsIjoiaVBob25lIDEzIn0="
}

REST: send the same keys in the POST /orders body, next to ID, IMEI and Qnt.

POST /orders body
{
  "ID": "12",
  "IMEI": "354800000012950",
  "Qnt": 1,
  "carrier": "AT&T",
  "model": "iPhone 13"
}

A required field that is missing is refused with its key and an example: Dhru missing field "carrier": send it in CUSTOMFIELD, e.g. {"carrier": "…"} (base64 JSON), REST 400 {"error": "missing field", "field": "carrier", "hint": …}. Nothing is charged.

For a server or remote service, if you leave IMEI out, its first field (other than a photo) is used as the order's value.

Photo fields (fieldtype image) take a one-time upload token, and uploading a photo needs your signed-in web account, not an API key. Order services with a photo field on the website.

Service types (SERVICETYPE)

Every service-list row carries SERVICETYPE: IMEI, SERVER or REMOTE. imeiservicelist lists IMEI services only. serverservicelist lists server services and remote ones, tagged REMOTE, since Dhru has no list action for remote services. When a server and a remote category share a name, the remote group's name ends in (Remote). REST GET /services keeps the three apart under imei, server and remote.

SERVICETYPE values
SERVICETYPEListed byIMEI carries
IMEIimeiservicelist; REST GET /services → imeiThe device's 15-digit IMEI, or its serial for serial-number services
SERVERserverservicelist; REST GET /services → serverThe value the service needs: a username, an email, a licence id…

Any type is placed with placeimeiorder or placeserverorder (REST POST /orders) and polled with getimeiorder or getserverorder. Types this store doesn't sell aren't listed.

First-use binding

When you create a key you choose what it is bound to: This IP, or The first server that calls, for when you don't know your panel's IP yet.

  • A first-use key has no IP yet. The first call that passes every check (right username and key, an active reseller account, an IP that isn't locked out) binds the key to that call's IP. From then on it works only from that IP, like any other key.
  • That first call must come within 24 hours of creating the key. After that the key is refused and revoked automatically: create a new one.
  • Make the first call from your panel's server, not a laptop. The panel's connection test, which calls accountinfo, is enough.
  • If two servers make the first call at the same moment, exactly one binds the key. The other gets the invalid credentials error, and the call shows as refused in the key's Recent calls.
  • When the key binds, we email you and add a notification with the IP ("Key ••••1234 is now bound to 203.0.113.10"). Not you? Revoke the key.
  • To move servers, use Reset binding on the key under Account → API keys: it waits for a first call again, for another 24 hours. Reset works only on first-use keys; for a key made for one IP, create a first-use key instead.

What's new for resellers

Additions only: nothing your panel already sends or reads has changed.

October 2026

  • Services with extra fields: send them in CUSTOMFIELD (Dhru) or in the POST /orders body (REST); each service-list row names them in Requires.Custom.
  • Every service-list row carries SERVICETYPE (IMEI, SERVER or REMOTE); serverservicelist also lists remote services, tagged REMOTE.
  • API keys can bind to the first server that calls them, within 24 hours of creating the key.
  • Services with a photo field are ordered on the website: uploading a photo needs your signed-in account.
  • Every product page shows its Order by API example with the exact field keys.

Migrating from the old API

If you already used imeifixer's API, nothing needs to change on your side:

What stays the same
WhatAfter the upgrade
URLsUnchanged: /api/index.php, /api/v1/services and /api/v1/services/api/index.php for Dhru panels, /api/rest/v1 for REST.
Actions and field namesUnchanged (username, apiaccesskey, action, parameters, ID, IMEI, Qnt …).
REFERENCEIDsOrders you placed before keep their REFERENCEID and still answer getimeiorder.
Service IDsUnchanged, so your panel's product mapping keeps working.
API keysYour keys carry over and stay bound to the same IP. Only replace one if we ask you to.
STATUS4 = success, 3 = rejected, as in every Dhru Fusion panel.

Need help?

Open a support ticket with the REFERENCEID, or the time of the call from Recent calls on your API keys page. Never send us your full API key. For a callback that gave up, quote its REFERENCEID and we can send it again.

Calls failing for everyone at once? Check the status page first.

Your cart

Total$0.00

Add funds

Between $5 and $5000, up to 2 decimal places.

Payment method

Card and crypto top-ups aren't available right now. You can ask for a bank transfer top-up on the Wallet page.