API docs

The REST API powers the web app, the open-source Go CLI at github.com/oppa26/ioncube-decode, and any integration you want to build. Async pattern: upload → poll status → download.

Base URL

https://console.decodephp.io

Authentication

All authenticated endpoints use the X-API-Key header. Your API key is your 16-digit account number, available in the console dashboard. API access is included for every account, and API decodes use the same credits as browser uploads.

X-API-Key: 1234567890123456

Failed API-key attempts are rate-limited: 5 failures per minute per IP returns 429 Too Many Requests.

End-to-end flow

A decode is three calls:

  1. Upload the file — returns a decode_id and queues the job.
  2. Poll status — returns pending,success, or failed. Poll roughly every 500 ms.
  3. Download the decoded bytes when status is success.

POST /accounts/upload/

Multipart form upload. Field name: file. Max size 10 MB. Must be a .php file.

curl https://console.decodephp.io/accounts/upload/ \
  -H "X-API-Key: 1234567890123456" \
  -F "file=@encoded.php"

Response (credits available, decode queued):

{
  "success": true,
  "message": "File queued for decoding.",
  "decode_id": 4821,
  "task_id": "a8b2c9d4-1234-5678-abcd-ef0123456789"
}

Response when out of credits (free-tier preview fallback):

HTTP/1.1 402 Payment Required

{
  "success": false,
  "preview": true,
  "message": "Preview only - purchase credits to download full file.",
  "preview_lines": ["<?php", "namespace App;", "..."],
  "total_lines": 240,
  "file_name": "encoded.php"
}

Error responses:

400 — No file provided / not a .php / file too large (>10MB)
400 — File is not IonCube encoded
400 — PHP version not supported
401 — Invalid API key
429 — Rate limit (5 failed API keys / minute)

GET /accounts/decode/<decode_id>/status/

Poll until status is success or failed.

curl https://console.decodephp.io/accounts/decode/4821/status/ \
  -H "X-API-Key: 1234567890123456"

Response while pending:

{
  "success": true,
  "decode_id": 4821,
  "status": "pending",
  "original_filename": "encoded.php",
  "php_version": "8.4",
  "ioncube_version": "15"
}

Response on success:

{
  "success": true,
  "decode_id": 4821,
  "status": "success",
  "original_filename": "encoded.php",
  "php_version": "8.4",
  "ioncube_version": "15",
  "decoded_size": 8912,
  "processing_time_ms": 1420,
  "download_url": "/accounts/decode/4821/download/",
  "view_url": "/accounts/decode/4821/view/",
  "opcodes_url": "/accounts/decode/4821/opcodes/"
}

Response on failure:

{
  "success": true,
  "decode_id": 4821,
  "status": "failed",
  "original_filename": "encoded.php",
  "php_version": "",
  "ioncube_version": "",
  "error_message": "Could not identify IonCube version"
}

GET /accounts/decode/<decode_id>/download/

Returns the decoded PHP source as application/x-php with a Content-Disposition: attachment header. Filename is <original>_decoded.php.

curl -OJ https://console.decodephp.io/accounts/decode/4821/download/ \
  -H "X-API-Key: 1234567890123456"

Error responses:

402 — Purchase credits to download (free-tier decode, preview only)
400 — Decoded file not available
404 — Not found / not owned by your account

GET /accounts/decode/<decode_id>/view/

Renders the decoded source in a browser viewer instead of downloading. Requires credits.

GET /accounts/decode/<decode_id>/opcodes/

Downloads the pretty-printed Zend opcode disassembly for the file. Useful for advanced reverse engineering. Requires credits.

GET /accounts/credits/

Current credit balance.

curl https://console.decodephp.io/accounts/credits/ \
  -H "X-API-Key: 1234567890123456"
{
  "available_credits": 47,
  "lifetime_remaining": null,
  "lifetime_limit": null,
  "purchased_credits": 47,
  "is_lifetime": false,
  "is_subscriber": false,
  "subscription_remaining": null,
  "subscription_limit": null
}

POST /accounts/preview/ (public)

Public endpoint used by the landing-page demo. Returns the first 20 lines of decoded output. Rate-limited to 30 preview requests per hour per IP.

The open-source Go CLI

The canonical reference implementation of this API is the Go CLI we publish on GitHub. It does upload, poll, download with a worker pool, and handles non-IonCube file skip detection.

# Install
go install github.com/oppa26/ioncube-decode@latest

# Single file
IONCUBE_API_KEY=1234567890123456 ioncube-decode encoded.php

# Directory (recursive), 8 concurrent workers, overwrite originals
IONCUBE_API_KEY=1234567890123456 ioncube-decode -w 8 -o ./src

Source: github.com/oppa26/ioncube-decode. MIT licensed.

Language examples

All examples assume DECODEPHP_KEY is your 16-digit API key.

PHP

<?php
$key = getenv('DECODEPHP_KEY');
$base = 'https://console.decodephp.io';

// 1. Upload
$ch = curl_init($base . '/accounts/upload/');
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['X-API-Key: ' . $key],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => ['file' => new CURLFile('encoded.php')],
    CURLOPT_RETURNTRANSFER => true,
]);
$uploaded = json_decode(curl_exec($ch), true);
$id = $uploaded['decode_id'];

// 2. Poll
while (true) {
    $ch = curl_init($base . "/accounts/decode/{$id}/status/");
    curl_setopt_array($ch, [
        CURLOPT_HTTPHEADER => ['X-API-Key: ' . $key],
        CURLOPT_RETURNTRANSFER => true,
    ]);
    $s = json_decode(curl_exec($ch), true);
    if ($s['status'] === 'success') break;
    if ($s['status'] === 'failed') exit('decode failed: ' . $s['error_message']);
    usleep(500000);
}

// 3. Download
$ch = curl_init($base . "/accounts/decode/{$id}/download/");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ['X-API-Key: ' . $key],
    CURLOPT_RETURNTRANSFER => true,
]);
file_put_contents('decoded.php', curl_exec($ch));

Python

import os, time, requests

key = os.environ['DECODEPHP_KEY']
base = 'https://console.decodephp.io'
headers = {'X-API-Key': key}

# 1. Upload
with open('encoded.php', 'rb') as f:
    up = requests.post(
        f'{base}/accounts/upload/',
        headers=headers,
        files={'file': f},
    ).json()
decode_id = up['decode_id']

# 2. Poll
while True:
    s = requests.get(
        f'{base}/accounts/decode/{decode_id}/status/',
        headers=headers,
    ).json()
    if s['status'] == 'success':
        break
    if s['status'] == 'failed':
        raise RuntimeError(s['error_message'])
    time.sleep(0.5)

# 3. Download
r = requests.get(
    f'{base}/accounts/decode/{decode_id}/download/',
    headers=headers,
)
with open('decoded.php', 'wb') as out:
    out.write(r.content)

Node.js

import fs from 'node:fs';

const key = process.env.DECODEPHP_KEY;
const base = 'https://console.decodephp.io';
const headers = { 'X-API-Key': key };

// 1. Upload
const form = new FormData();
form.append('file', new Blob([fs.readFileSync('encoded.php')]), 'encoded.php');
const upRes = await fetch(`${base}/accounts/upload/`, {
  method: 'POST', headers, body: form,
});
const { decode_id } = await upRes.json();

// 2. Poll
while (true) {
  const s = await (await fetch(
    `${base}/accounts/decode/${decode_id}/status/`,
    { headers },
  )).json();
  if (s.status === 'success') break;
  if (s.status === 'failed') throw new Error(s.error_message);
  await new Promise(r => setTimeout(r, 500));
}

// 3. Download
const dl = await fetch(
  `${base}/accounts/decode/${decode_id}/download/`,
  { headers },
);
fs.writeFileSync('decoded.php', Buffer.from(await dl.arrayBuffer()));

Limits

  • Maximum file size: 10 MB.
  • Public preview: 30 requests / hour / IP.
  • Failed API-key attempts: 5 / minute / IP.
  • Concurrent decodes: bounded by your credit balance. The CLI default is 4 parallel workers — safe and fast.

Error codes

400 — Bad request (missing file, wrong MIME, too large, not IonCube, unsupported PHP)
401 — Invalid API key
402 — Out of credits (free-tier preview returned instead)
404 — Decode not found or not owned by your account
429 — Rate-limited
500 — Internal error (retry)
504 — Processing timed out (opcode export only)