Create an account or sign in, open Developer → Your API keys, name your key and copy it. The secret is shown once.
Send Authorization: Bearer YOUR_API_KEY with every request. Keep it in a server environment variable.
Upload an encoded PHP file or ZIP with a decoder selection.
Poll the returned job URL until complete, partial or failed, or receive a signed webhook. Download the available result.
Use GET /me before uploading to check your current access and upload limits. A cookie login does not authenticate API endpoints. Cross-origin browser access is not enabled: call this API from your server.
# Store the key securely; do not paste it into source control.
export ASEWEB_API_KEY='YOUR_API_KEY'
curl --fail-with-body https://aseweb.ir/api/v1/me \
-H "Authorization: Bearer $ASEWEB_API_KEY"
curl --fail-with-body https://aseweb.ir/api/v1/jobs \
-H "Authorization: Bearer $ASEWEB_API_KEY" \
-F 'decoder=ioncube' -F 'file=@encoded.php'
# Replace JOB_ID with job.id from the response. Poll at most once every 3 seconds.
curl --fail-with-body https://aseweb.ir/api/v1/jobs/JOB_ID \
-H "Authorization: Bearer $ASEWEB_API_KEY"
# Only after status is complete or partial:
curl --fail-with-body https://aseweb.ir/api/v1/jobs/JOB_ID/download \
-H "Authorization: Bearer $ASEWEB_API_KEY" -o result.php
02 / ACCESS
Your key follows your account
Capability
Free / expired subscription
Active subscription
Endpoints & decoder selection
Same endpoints, both decoders
Same endpoints, both decoders
PHP output
First 15 formatted lines, up to 1,500 Unicode characters
Full reconstructed PHP
ZIP output
Preview of the first successfully decoded PHP file; no ZIP download
Complete available ZIP with folder structure
JSON preview
Limited prefix, truncated: true
Full first decoded PHP file, truncated: false
Decode credits
Not spent
Not spent
Upload rate
5 per hour per account; 20 per hour per IP
60 per minute per account
Full API access is included with every subscription: 7 days for $24, 10 days for $29 or 30 days for $69. Buying file credits alone does not enable full API access. Choose a subscription →
The same key upgrades automatically when your plan activates and returns to demo access when it expires. A request submitted as a demo remains a demo after upgrading; submit the file again for full output. Full API downloads require an active plan, including downloads of previously full jobs. This does not change paid website downloads.
Use up to five active keys per account. Revoke a key from Developer to stop it immediately. Banned or deleted accounts cannot use keys. Treat the secret like a password; never put it in a URL, public frontend, screenshots or logs.
03 / REFERENCE
API endpoints
Every endpoint below includes the complete URL, headers, body and a ready-to-copy curl command. Use the same URLs with free and subscription keys.
Set your key once
Replace YOUR_API_KEY with the key from Developer → Your API keys. For job-specific calls, replace JOB_ID_FROM_RESPONSE with job.id returned by the upload. Keep the key on your server.
Example values; some responses show selected fields. See the OpenAPI specification for the complete schema.
POST202 Accepted · JSON
Upload a file to decode
Send one encoded PHP file or ZIP archive. The same URL supports ionCube and SourceGuardian; the decoder field selects the format. Your API key determines demo or full output.
Endpoint
https://aseweb.ir/api/v1/jobs
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
application/json
Recommended
Content-Type
multipart/form-data; boundary=…
Automatic — set by curl / your HTTP client
Body
multipart/form-data Send form fields, not a JSON body.
Field
Value
Requirement
decoder
ioncube or sourceguardian
Required · string
file
An encoded .php file or .zip archive.
Required · binary file
Use curl’s --form option. It generates the Content-Type and boundary automatically; do not add a manual Content-Type header.
Save job.id from the response and use it in the status, preview and download URLs. HTTP 202 means accepted, even when the response already says complete. The Location and Retry-After headers are also returned. Upload POSTs are not idempotent: check history before retrying after a timeout.
Example values; some responses show selected fields. See the OpenAPI specification for the complete schema.
GET200 OK · JSON
List your API decode history
Returns this account’s API submissions, newest first, in pages of 50. All keys for the same account share this history. Website-only jobs are not included.
Endpoint
https://aseweb.ir/api/v1/jobs
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
application/json
Recommended
Query parameter
Name
Value
Requirement
cursor
Use nextCursor from the previous page: https://aseweb.ir/api/v1/jobs?cursor=NEXT_CURSOR. Stop when nextCursor is null.
Example values; some responses show selected fields. See the OpenAPI specification for the complete schema.
GET200 OK · JSON
Check a decode request
Poll this endpoint until the job is complete, partial or failed. Wait at least three seconds between polls; while queued, wait 15–30 seconds. Queue estimates are optional and may change.
Endpoint
https://aseweb.ir/api/v1/jobs/{id}
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
application/json
Recommended
Path parameter
Name
Value
Requirement
id
The 32-character job.id returned by POST https://aseweb.ir/api/v1/jobs. Replace {id} in the URL.
Statuses: submitting, queued, decoding, complete, partial or failed. Inspect per-file results for a partial archive. Failed jobs return an error and no downloadable result.
Example values; some responses show selected fields. See the OpenAPI specification for the complete schema.
GET200 OK · JSON
Preview the decoded source
Returns decoded PHP as JSON. Demo access returns up to 15 formatted lines and 1,500 Unicode characters. Full access returns the complete selected PHP file. For ZIPs, the first successfully decoded PHP file is selected.
Endpoint
https://aseweb.ir/api/v1/jobs/{id}/preview
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
application/json
Recommended
Path parameter
Name
Value
Requirement
id
The 32-character job.id returned by POST https://aseweb.ir/api/v1/jobs. Replace {id} in the URL.
HTTP 409 means the output is not available yet; check job status. Demo prefixes are intentionally incomplete and may end inside a statement. truncated: true always marks demo output.
Example values; some responses show selected fields. See the OpenAPI specification for the complete schema.
GET200 OK · file attachment
Download the decoded result
Returns an attachment. Full access downloads the complete available PHP file or ZIP. Demo access downloads only a plain-text PHP preview, including when the uploaded file was a ZIP.
Endpoint
https://aseweb.ir/api/v1/jobs/{id}/download
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
*/*
Recommended
Path parameter
Name
Value
Requirement
id
The 32-character job.id returned by POST https://aseweb.ir/api/v1/jobs. Replace {id} in the URL.
The command saves result.php. For a full decoded ZIP, change it to --output result.zip. Check Content-Disposition for the output filename and X-AseWeb-Access: demo or full for the response access. HTTP 409 means not ready; HTTP 410 means the file expired.
Response
200 OK · file attachment
A PHP or ZIP file stream for full access, or a text preview for demo access. The response is not JSON.
GET200 OK · JSON
Inspect webhook delivery
See the notification state and every delivery attempt for an API job owned by your account. The callback URL and private delivery fields are never returned.
Endpoint
https://aseweb.ir/api/v1/jobs/{id}/webhook
Headers
Header
Value
Requirement
Authorization
Bearer YOUR_API_KEY
Required
Accept
application/json
Recommended
Path parameter
id: the 32-character job ID returned by your upload.
States: waiting for decode completion, pending for delivery/retry, delivered, exhausted or cancelled. A null HTTP status means no response was received. An unfinished attempt has a null finish time. HTTP 404 means the job is not yours or no webhook was enabled for this submission. Upload and single-job status responses also include job.webhook (null when absent).
04 / INTEGRATE
Working examples
Python · SourceGuardian PHP
# pip install requests
import os, time
import requests
base = "https://aseweb.ir/api/v1"
session = requests.Session()
session.headers["Authorization"] = "Bearer " + os.environ["ASEWEB_API_KEY"]
with open("encoded.php", "rb") as file:
response = session.post(base + "/jobs", data={"decoder": "sourceguardian"},
files={"file": file}, timeout=120)
response.raise_for_status()
job = response.json()["job"]
while job["status"] not in ("complete", "partial", "failed"):
time.sleep(5 if job["status"] != "queued" else 15)
response = session.get(base + "/jobs/" + job["id"], timeout=60)
response.raise_for_status()
job = response.json()["job"]
if job["status"] == "failed":
raise RuntimeError(job.get("error") or "Decode failed")
response = session.get(base + "/jobs/" + job["id"] + "/preview", timeout=60)
response.raise_for_status()
print(response.json()["source"])
response = session.get(base + "/jobs/" + job["id"] + "/download", timeout=120)
response.raise_for_status()
with open("result.php", "wb") as file: # use result.zip for full ZIP uploads
file.write(response.content)
JavaScript · ionCube ZIP
// Node.js 22+, run on your server. No extra packages.
import { readFile, writeFile } from "node:fs/promises";
const base = "https://aseweb.ir/api/v1";
const headers = { Authorization: `Bearer ${process.env.ASEWEB_API_KEY}` };
async function call(path, options = {}) {
const response = await fetch(base + path, { ...options, headers });
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
return response;
}
const form = new FormData();
form.set("decoder", "ioncube");
form.set("file", new File([await readFile("project.zip")], "project.zip"));
let { job } = await (await call("/jobs", { method: "POST", body: form })).json();
while (!["complete", "partial", "failed"].includes(job.status)) {
await new Promise(resolve => setTimeout(resolve, job.status === "queued" ? 15000 : 5000));
({ job } = await (await call(`/jobs/${job.id}`)).json());
}
if (job.status === "failed") throw new Error(job.error || "Decode failed");
const output = await call(`/jobs/${job.id}/download`);
const filename = output.headers.get("x-aseweb-access") === "demo" ? "preview.php" : "decoded.zip";
await writeFile(filename, Buffer.from(await output.arrayBuffer()));
Neither example retries submission automatically. Handle timeouts by checking your job history, and handle 429 responses with a delay. Keep long-running polling within your own application’s timeout policy.
05 / NOTIFICATIONS
Just save your webhook URL
No webhook secret or signature setup is needed. Save your endpoint once and receive a JSON notification when your API decode finishes.
Click Test webhook to check delivery. You’ll see its status, HTTP result, time and any error. Tests use no credits.
Upload files normally. Future API uploads automatically use your saved URL, across all your API keys and both decoders.
Clear the URL and save to stop notifications. Changing or removing it cancels pending deliveries to the previous URL. No callback fields are needed in upload requests.
Use decoder=sourceguardian for SourceGuardian or file=@project.zip for an archive. GET https://aseweb.ir/api/v1/me includes webhooks.configured and webhooks.enabled. Individual job responses include delivery state under job.webhook.
What your URL receives
AseWeb sends an HTTP POST with Content-Type: application/json when the job becomes complete, partial or failed, including queued completions. A ZIP produces one summary notification; use job status for individual files.
The event types are decode.complete, decode.partial and decode.failed. Manual tests use webhook.test with data.test: true and a short message. A test ID is not a decode job ID and has no downloadable file.
Notifications contain status and authenticated download links. Downloads continue to follow the account’s API access: demo output for free accounts and complete results for active subscriptions. An API key is still required to fetch a result.
Simple receiver example
This Node.js 22.13+ example accepts JSON, saves each event once in SQLite and returns HTTP 202. Test events are acknowledged without queuing a download. Put it behind HTTPS and save its public URL in Developer. Download the example →
// Node.js 22.13+. Put behind your HTTPS reverse proxy on port 443.
// Save https://your-domain.com/webhooks/aseweb in AseWeb's Developer panel.
import { createServer } from 'node:http';
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(process.env.WEBHOOK_DB || 'webhooks.sqlite');
db.exec(`CREATE TABLE IF NOT EXISTS received_events (
id TEXT PRIMARY KEY, body TEXT NOT NULL, received_at INTEGER NOT NULL,
processed_at INTEGER
)`);
const enqueue = db.prepare('INSERT OR IGNORE INTO received_events(id,body,received_at) VALUES(?,?,?)');
const enqueueTest = db.prepare('INSERT OR IGNORE INTO received_events(id,body,received_at,processed_at) VALUES(?,?,?,?)');
createServer(async (req, res) => {
if (req.method !== 'POST' || req.url !== '/webhooks/aseweb') {
res.writeHead(404).end(); return;
}
try {
const chunks = []; let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > 65536) { res.writeHead(413).end(); return; }
chunks.push(chunk);
}
const raw = Buffer.concat(chunks).toString('utf8');
let event;
try { event = JSON.parse(raw); }
catch { res.writeHead(400).end(); return; }
if (!event || !/^[a-f0-9]{32}$/.test(event.id) ||
!['decode.complete', 'decode.partial', 'decode.failed', 'webhook.test'].includes(event.type) ||
!/^[a-f0-9]{32}$/.test(event.data?.id)) {
res.writeHead(400).end(); return;
}
// Save each event once before acknowledging it. Your worker can confirm
// decode status and download files through the authenticated AseWeb API.
const now = Date.now();
if (event.type === 'webhook.test') enqueueTest.run(event.id, raw, now, now);
else enqueue.run(event.id, raw, now);
res.writeHead(202).end();
} catch {
// Storage failures must not acknowledge delivery; AseWeb can then retry.
res.writeHead(500).end();
}
}).listen(Number(process.env.PORT || 3000), '127.0.0.1', () => {
console.log('Webhook receiver ready');
});
node webhook-receiver.mjs
Callbacks are unsigned notifications. Use the authenticated job-status endpoint for authoritative status before processing or downloading a result. The event ID lets your receiver handle duplicate deliveries.
Delivery and retries
Return any HTTP 2xx response within 10 seconds to acknowledge delivery.
Network/DNS failures, timeouts and non-2xx responses trigger retries. Decode notifications have up to 8 attempts total, with retry delays of approximately 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours and 12 hours after the preceding failed attempt.
Queued completions and due retries are checked every minute; load can delay dispatch. The same event ID and JSON body are used for retries. Delivery may happen before the upload response reaches you.
Manual tests make one delivery attempt, with no automatic retry. Limits are three tests per minute and ten per hour per account. The latest test result survives page refreshes.
Only public HTTPS hostnames on port 443 are accepted. Redirects, private destinations, IP literals, credentials in URLs and AseWeb service URLs are blocked.
HTTP-rejected submissions do not trigger completion notifications. A webhook failure does not change the decode result; polling and authenticated downloads remain available.
Delivery is sent through Cloudflare and has no fixed dedicated sender IP. Informational headers include User-Agent: AseWeb-Webhooks/1.0, X-AseWeb-Event-ID, X-AseWeb-Timestamp (Unix seconds) and X-AseWeb-Attempt. JSON timestamps use Unix milliseconds.
Inspect delivery state and attempts at GET https://aseweb.ir/api/v1/jobs/{id}/webhook. Administrators can inspect decode notifications and manual tests under Webhooks.
06 / OPERATE
Limits, retention & errors
Use GET /me for the live upload limits. Defaults: 2 MB per PHP file, 20 MB per ZIP and 1,000 PHP files per ZIP. Administrators can change these.
120 API requests per minute per account across all keys, plus the upload rates above. HTTP 429 means the current minute/hour window is full. Wait for the window to reset.
Files and preview text expire after seven days; history remains. Expired output returns HTTP 410. Download promptly.
No endpoint executes your PHP. Results are reconstructed source; comments, original formatting and some original names may be absent. Compatibility varies by protected file. Test the result in your own environment.
Readable PHP, wrong-decoder uploads, unknown binaries and invalid archives are rejected. ZIPs containing no decodable files cannot return a demo. Files needing unavailable protection keys may require their companion files in the same ZIP.
Decoded archives may be partial. Check status and the file list before assuming every member succeeded. Demo ZIP previews do not expose a complete per-file report.
Only your account’s API jobs are accessible. A revoked key, banned account or deleted account returns HTTP 401. This API does not expose admin or billing operations.
HTTP
Meaning / action
202
Upload accepted. Read job.status, then poll or wait for your configured webhook.
400
Invalid input, unsupported file or wrong decoder. Correct the upload.
401
Missing/invalid/revoked key or inactive account.
403
Access denied by an IP ban (code: ip_banned), or a subscription changed during upload. For an IP ban, contact support; otherwise recheck /me.
404
Unknown route or job not owned by this account.
409
Output not ready or submission still being accepted. Poll status.
410
File/preview expired. Submit a fresh request.
413 / 415
Upload too large / wrong content type. Use multipart/form-data and live limits.
429
Rate limited. Back off; do not generate more keys to bypass limits.
500 / 503
Server error / temporarily unavailable. Retry status reads later; check history before retrying an upload.
{"error":"Decoded output is not available"}
Responses include X-Request-ID. Share that ID with @amirmalek0 for support; never send your API key. Keep response bodies and keys out of public logs.