AI TuningFiles | ALPHA LLC – send tuning files from your own portal software and get the finished files back automatically.
https://portal.ai-tuningfiles.com/api/v1.php?r=<endpoint>https://portal.ai-tuningfiles.com/api/v1.php?r=files/123. If your server passes PATH_INFO, https://portal.ai-tuningfiles.com/api/v1.php/files/123 works as well.Authorization: Bearer fsp_xxxxxxxx… or X-Api-Key: fsp_xxxxxxxx…Content-Type: application/json, file as file_base64) or as multipart/form-data.2026-10-08T09:12:00+02:00.fsp_test_ run in a sandbox: same checks as live, no credits booked, no real file created (see Test mode).
POST files (vehicle, ECU, stage / options, original read).GET account) and process the file – automatically or by our tuners.file.completed). You download the modified file with GET files/{id}/download/modified and deliver it to your dealer.file.message; you answer with POST files/{id}/messages.POST files/{id}/options – booked and delivered like a new order, no new upload.No webhook possible? Poll GET files?updated_since=… every few minutes instead.
?r=accountYour credit balance.
{ "portal": "Tuning Partner GmbH", "credits": 120.5, "credit_limit": 0, "available": 120.5, "webhook_url": "https://…" }
?r=services optional &vehicle_type=CarsAll stages and options with your price in credits, per vehicle type. Use the id (or the name) when you create a file.
{ "currency": "credits",
"vehicle_types": [ { "vehicle_type": { "id": 1, "name": "Cars" }, "max_per_file": 8,
"services": [ { "id": 2, "type": "stage", "name": "Stage 1", "winols_name": "Stage1", "description": "", "credits": 3 },
{ "id": 9, "type": "option", "name": "Pop & Bang", "winols_name": "PopBang", "description": "", "credits": 1 },
{ "id": 11, "type": "option", "name": "Start/Stop off", "winols_name": "StartStop", "description": "", "credits": 1 } ] } ] }
max_per_file – maximum price of one file for this vehicle type (null = no maximum): however many solutions you order, a file never costs more. Also counts for options added later with POST files/{id}/options.
?r=files| Field | Required | Description |
|---|---|---|
file | yes | Original read, max. 15 MB. JSON: file_base64 + file_name; multipart: field file. Encrypted slave files are not accepted – send the decoded read. |
make, model, engine | yes | Vehicle, e.g. Volkswagen, Golf, 2.0 TDI 150hp |
stage | stage or options | Stage id or name from GET services |
options | stage or options | Option ids or names – JSON array ("options": ["Pop & Bang", 11]), multipart array (options[]=9&options[]=11) or comma list |
kind | no | ecu (engine, default), tcu (gearbox) or unit (Mercedes CPC Tune – services with "type": "unit", same rules as TCU). TCU / DSG solutions (GET services, "type": "tcu") are sent in options – exactly one per file and never together with a stage or engine options (error mixed_kinds). Without kind it is set automatically from the solutions. |
vehicle_type | no | Id or name, default: first type (Cars) |
generation, year, gearbox | no | More vehicle data |
vin, plate | no | VIN and licence plate – only send them if you need them (see Privacy) |
waiting | no | Delivery time: now (customer waiting), 1-2, 3-4 (hours) or next (next day) |
fuel | no | diesel or petrol – solutions only offered for the other fuel are refused (field fuel in GET services: empty = both) |
ecu, ecu_hw, ecu_sw | no | ECU name, hardware and software number (we also read them from the file) |
tool, read_method | no | Read tool and method, e.g. Autotuner, OBD |
note | no | Notes for the tuner (DTC codes, hardware changes …) |
dtc_codes | no | Fault codes to switch off – JSON array (["P2002","P20EE"]) or a ; / , list. Only used when the DTC option is among your options. Read the codes of the file first with POST dtc/read (see DTC removal). Without codes the DTC option is handled by hand. |
customer | no | Name of your dealer, shown to our tuner – a company name or your own customer number is enough (see Privacy) |
reference | no | Your own order id (max. 100 characters, unique) – returned in every response and webhook |
Response 201:
{ "file": { "id": 18452, "number": "1791450304988", "reference": "A-1001", "status": "pending",
"vehicle": { "type": "Cars", "make": "Volkswagen", "model": "Golf", "generation": "Golf 7", "engine": "2.0 TDI 150hp", "year": "2016", … },
"ecu": { "name": "Bosch EDC17C64", "hw": "", "sw": "" }, "tool": "Autotuner", "read_method": "OBD",
"services": [ { "id": 2, "name": "Stage 1" }, { "id": 9, "name": "Pop & Bang" } ],
"credits": 4, "note": "", "unread_messages": 0, "modified_available": false, "modified_name": null,
"created_at": "2026-10-08T09:12:00+02:00", "updated_at": "…", "completed_at": null },
"balance": 116.5 }
Status values: pending (waiting), in_progress, completed, cancelled. Many files are completed within seconds – then the first response already says completed.
?r=filesFilters: status, reference, updated_since, page, per_page (max. 100). Newest first. total is the number of all matching files.
updated_since is an ISO 8601 date-time with time zone, e.g. 2026-10-08T09:00:00Z or 2026-10-08T11:00:00+02:00 (URL-encode the + as %2B). Returns files changed at or after that moment. For polling, store the highest updated_at you received and send it next time.
{ "total": 2, "page": 1, "per_page": 25, "files": [ { …file… }, { …file… } ] }
?r=files/{id}One file, same format as above.
?r=files/{id}/download/modifiedThe tuned file (binary, Content-Disposition with file name). Before it is ready: 409 not_ready.
?r=files/{id}/download/originalThe original you sent.
?r=files/{id}/messages{ "messages": [ { "id": 99, "from": "system", "text": "File received via API", "attachment": null, "created_at": "…" },
{ "id": 101, "from": "support", "text": "Please send the full read incl. EEPROM", "attachment": null, "created_at": "…" },
{ "id": 102, "from": "you", "text": "Here it is", "attachment": { "name": "eeprom.bin" }, "created_at": "…" } ] }
?r=files/{id}/messages/{message_id}/attachmentAttachment of a message (e.g. a new version from our tuner).
?r=files/{id}/messagesFields: text and/or attachment (multipart file). Response 201 with the new message.
?r=files/{id}/cancelOptional reason. Only possible while the file is still pending and nobody works on it – the credits are refunded. Otherwise 409 cannot_cancel: write a message instead.
?r=files/{id}/complaintField reason (required) – for a delivered file that does not work as expected. Our tuner is informed and answers in the messages; a corrected file comes as a new file.completed webhook (or as an attachment in file.message).
?r=files/{id}/optionsOrder additional options for a file you already sent (e.g. your customer wants AdBlue off after Stage 1). Field options = array of option ids or names from GET services (or a comma list), optional note (text for our tuner). Only "type": "option" – a different stage or a TCU / CPC solution needs a new file.
GET services), booked from your credit balance at once – never more than max_per_file for the whole file. A later cancel refunds it together with the file."delivered": true and a file.completed webhook – download it as usual with download/modified."delivered": false: the file goes back to our tuners (status in_progress), you get file.completed as soon as the new version is ready. Your customer keeps the previous file until then.POST ?r=files/1234/options
{ "options": [14], "note": "Customer also wants AdBlue off" }
HTTP 201
{ "added": [ { "id": 14, "name": "AdBlue / SCR off", "credits": 3 } ],
"charged": 3, "delivered": true, "balance": 117.5, "file": { …file… } }
Errors: already_ordered (409), cannot_change (409, cancelled file), invalid_option, invalid_kind, fuel_mismatch, not_offered (422), insufficient_credits (402 – nothing is booked).
| Your customer … | What you do |
|---|---|
| needs a change on the same file (e.g. less power, a different setting) | POST files/{id}/messages with text (and optionally a new read as attachment). The file is reopened on our side; the revised version comes as file.completed or as an attachment in file.message. No extra credits for a normal revision. |
| reports that the file does not work | POST files/{id}/complaint with reason. |
| wants an additional option (e.g. AdBlue off, DPF off) | POST files/{id}/options – price of the option is booked, new version as above. |
| has another car, another ECU or a different stage | New order with POST files. |
Available only if your account has DTC enabled – then the DTC option appears in GET services. The reading and the switch-off run completely on our side.
?r=dtc/readSend a read (file as multipart, or file_base64 in JSON; optionally ecu and make to speed up the detection). You get back the fault codes found in the file – show them to your dealer so he can choose which ones to switch off. Reading the codes is free.
{ "ecu": "Bosch MD1CS014", "count": 2,
"codes": [ { "code": "P2002", "status": "on", "description": "Particulate filter efficiency below threshold" },
{ "code": "P20EE", "status": "on", "description": "NOx catalyst efficiency below threshold" } ] }
Then send the order with POST files: add the DTC option to options and the chosen codes as dtc_codes. The file is delivered with exactly those codes switched off; the price per file is the price of the DTC option. If you order the DTC option without dtc_codes, it is handled by hand (write the codes into note).
We send a POST with a JSON body to the webhook URL you gave us:
| Event | When |
|---|---|
file.completed | The modified file is ready (also after a rework or added options) |
file.message | Our tuner wrote a message – included as message |
file.cancelled | We cancelled the file (credits refunded) |
ping | Test from our side |
POST https://your-portal.com/fileservice-webhook.php
X-Event: file.completed
X-Event-Id: 5521
X-Timestamp: 1791450790 (Unix time when we sent it)
X-Signature-256: sha256=9b2e… (HMAC-SHA256 of X-Timestamp + "." + raw body, key = your webhook secret)
X-Signature: sha256=3f1c… (old: HMAC of the body only – kept for existing integrations)
{ "event": "file.completed", "sent_at": "2026-10-08T09:13:10+02:00", "file": { …file… } }
Answer with HTTP 2xx within 8 seconds. Otherwise we retry after 1, 5, 15, 60 minutes, 3, 6 and 12 hours. Events can arrive more than once – use X-Event-Id to ignore duplicates. Always check the signature and the time stamp (reject anything older than 5 minutes – so a recorded webhook cannot be replayed later). Each retry is signed with a fresh time stamp.
<?php
$secret = 'YOUR_WEBHOOK_SECRET';
$body = file_get_contents('php://input');
$ts = (int)($_SERVER['HTTP_X_TIMESTAMP'] ?? 0);
$sig = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);
if (abs(time() - $ts) > 300 || !hash_equals($sig, $_SERVER['HTTP_X_SIGNATURE_256'] ?? '')) { http_response_code(401); exit; }
$e = json_decode($body, true);
if ($e['event'] === 'file.completed') {
// download the modified file and give it to your dealer (see PHP example below)
}
http_response_code(200);
Per API key: 120 requests per minute. Per IP address: 300 requests per minute (all keys together). Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Above the limit you get 429 rate_limited with a Retry-After header (seconds) – wait that long, then retry. Polling every 2–5 minutes is plenty; webhooks are better.
HTTP 402
{ "error": { "code": "insufficient_credits", "message": "Not enough credits: 4 needed, 1.5 available.", "needed": 4, "balance": 1.5 } }
| HTTP | code |
|---|---|
| 401 | unauthorized – key missing, wrong or deactivated |
| 402 | insufficient_credits – please top up |
| 404 | not_found, unknown_endpoint, file_missing |
| 409 | duplicate_reference (the existing file is returned), not_ready, cannot_cancel, cannot_complain, already_ordered, cannot_change |
| 400 | invalid_json, invalid_parameter |
| 413 | file_too_large |
| 422 | missing_field, missing_file, invalid_file, invalid_stage, invalid_option, invalid_vehicle_type, fuel_mismatch, slave_not_supported, not_offered, mixed_kinds, invalid_kind |
| 429 | rate_limited – see Rate limits |
| 500 | server_error – please retry later |
Ask us for a test key (fsp_test_…) to build and test your integration. It works like a live key, with these differences:
GET account always shows 1000 test credits.completed immediately, the "modified" file is a copy of your original.900000001 and numbers like TEST-12; every response and webhook has "test": true.file.completed (and file.cancelled after a cancel) are sent to the test key's webhook URL – signed like live ones.delivered: true + file.completed right away), but nothing reaches our tuners. Test files are deleted after 7 days.Send only the data the tuner needs. vin, plate and customer are optional – the file is processed without them. If you send them, use a company name or your own customer number for customer, never private persons' details.
We store order data and files as long as needed for processing, rework and statutory retention periods, and use them only to process your orders. If you send personal data of your customers, a data processing agreement (DPA / AVV under Art. 28 GDPR) may be required – please contact us and we will send it.
<?php
const API = 'https://portal.ai-tuningfiles.com/api/v1.php';
const KEY = 'fsp_…'; // your API key – keep it on the server, never in the browser
function fs_api(string $method, string $route, ?array $data = null) {
$ch = curl_init(API . '?r=' . $route);
$head = ['Authorization: Bearer ' . KEY];
if ($data !== null) { $head[] = 'Content-Type: application/json'; curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); }
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 120, CURLOPT_HTTPHEADER => $head]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$type = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
return [$code, str_contains($type, 'json') ? json_decode($body, true) : $body];
}
// 1. send a file
[$code, $r] = fs_api('POST', 'files', [
'file_base64' => base64_encode(file_get_contents('/path/to/original.bin')),
'file_name' => 'original.bin',
'make' => 'Volkswagen', 'model' => 'Golf', 'engine' => '2.0 TDI 150hp', 'year' => '2016',
'fuel' => 'diesel', 'ecu' => 'Bosch EDC17C64', 'tool' => 'Autotuner', 'read_method' => 'OBD',
'stage' => 'Stage 1', 'options' => ['Pop & Bang', 'Start/Stop off'],
'customer' => 'D-2041', 'reference' => 'A-1001',
]);
if ($code !== 201) die($r['error']['message']);
$id = $r['file']['id'];
// 2. later (webhook file.completed or polling): download the tuned file
[$code, $bin] = fs_api('GET', "files/$id/download/modified");
if ($code === 200) file_put_contents("/path/to/tuned_$id.bin", $bin);
// 3. answer a question of our tuner
fs_api('POST', "files/$id/messages", ['text' => 'Car has a downpipe, no DPF.']);
Multipart works too ('file' => new CURLFile(…), 'options[]' => …) – JSON is simpler for arrays.
curl: curl -H "Authorization: Bearer fsp_…" "https://portal.ai-tuningfiles.com/api/v1.php?r=account"
| Date | Change |
|---|---|
| 2026-10-24 | New: DTC removal – POST dtc/read reads the fault codes of a file, order them off with the DTC option + dtc_codes in POST files. Available when the DTC option is offered to your account. |
| 2026-10-21 | New: POST files/{id}/options – add options to an existing file (price booked, new version via file.completed). New section Revisions. GET services: max_per_file per vehicle type. Fix: webhooks file.completed / file.message / file.cancelled were not sent for some files. |
| 2026-10-14 | Mercedes CPC Tune: services with "type": "unit", kind = unit – one per file, own file like TCU. |
| 2026-10-11 | TCU / DSG files: services with "type": "tcu", optional field kind (ecu/tcu), file JSON has kind; errors mixed_kinds, invalid_kind. |
| 2026-10-10 | Webhooks: new headers X-Timestamp + X-Signature-256 (time stamp signed, replay protection). X-Signature stays for now. · Rate limit per key (120/min) with X-RateLimit-* headers and 429 rate_limited. · Test keys (fsp_test_…) with sandbox. · updated_since described as ISO 8601. · Privacy notes. |
| 2026-10-09 | Fields fuel (diesel/petrol) and waiting (delivery time); error fuel_mismatch. |
| 2026-10-07 | Version 1 released. |
Changes within v1 are always backwards compatible (new fields and headers only). Breaking changes come as a new version (v2) with advance notice.