Partner API v1

AI TuningFiles | ALPHA LLC – send tuning files from your own portal software and get the finished files back automatically.

Base URL
https://portal.ai-tuningfiles.com/api/v1.php?r=<endpoint>
Example: 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.

Authentication – every request needs your API key (you get it from us):
Authorization: Bearer fsp_xxxxxxxx…  or  X-Api-Key: fsp_xxxxxxxx…

Format – responses are JSON (UTF-8). Send fields as JSON (Content-Type: application/json, file as file_base64) or as multipart/form-data.
Dates – all date-times are ISO 8601 with time zone offset, e.g. 2026-10-08T09:12:00+02:00.

Test key – keys starting with fsp_test_ run in a sandbox: same checks as live, no credits booked, no real file created (see Test mode).

How it works

  1. Your dealer uploads a file in your portal. Your software sends it to us with POST files (vehicle, ECU, stage / options, original read).
  2. We book the price from your credit balance (prepaid, see GET account) and process the file – automatically or by our tuners.
  3. When it is ready we call your webhook (file.completed). You download the modified file with GET files/{id}/download/modified and deliver it to your dealer.
  4. Questions from our tuners arrive as file.message; you answer with POST files/{id}/messages.
  5. Your customer wants more on the same file (e.g. AdBlue off)? Order it with 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.

Account

GET?r=account

Your credit balance.

{ "portal": "Tuning Partner GmbH", "credits": 120.5, "credit_limit": 0, "available": 120.5, "webhook_url": "https://…" }

Services & prices

GET?r=services optional &vehicle_type=Cars

All 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.

Send a file

POST?r=files
FieldRequiredDescription
fileyesOriginal read, max. 15 MB. JSON: file_base64 + file_name; multipart: field file. Encrypted slave files are not accepted – send the decoded read.
make, model, engineyesVehicle, e.g. Volkswagen, Golf, 2.0 TDI 150hp
stagestage or optionsStage id or name from GET services
optionsstage or optionsOption ids or names – JSON array ("options": ["Pop & Bang", 11]), multipart array (options[]=9&options[]=11) or comma list
kindnoecu (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_typenoId or name, default: first type (Cars)
generation, year, gearboxnoMore vehicle data
vin, platenoVIN and licence plate – only send them if you need them (see Privacy)
waitingnoDelivery time: now (customer waiting), 1-2, 3-4 (hours) or next (next day)
fuelnodiesel or petrol – solutions only offered for the other fuel are refused (field fuel in GET services: empty = both)
ecu, ecu_hw, ecu_swnoECU name, hardware and software number (we also read them from the file)
tool, read_methodnoRead tool and method, e.g. Autotuner, OBD
notenoNotes for the tuner (DTC codes, hardware changes …)
dtc_codesnoFault 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.
customernoName of your dealer, shown to our tuner – a company name or your own customer number is enough (see Privacy)
referencenoYour 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.

List files

GET?r=files

Filters: 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… } ] }
GET?r=files/{id}

One file, same format as above.

Downloads

GET?r=files/{id}/download/modified

The tuned file (binary, Content-Disposition with file name). Before it is ready: 409 not_ready.

GET?r=files/{id}/download/original

The original you sent.

Messages

GET?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": "…" } ] }
GET?r=files/{id}/messages/{message_id}/attachment

Attachment of a message (e.g. a new version from our tuner).

POST?r=files/{id}/messages

Fields: text and/or attachment (multipart file). Response 201 with the new message.

Cancel

POST?r=files/{id}/cancel

Optional 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.

Complaint / rework

POST?r=files/{id}/complaint

Field 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).

Add options to a file

POST?r=files/{id}/options

Order 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.

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).

Revisions

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 workPOST 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 stageNew order with POST files.

DTC removal

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.

POST?r=dtc/read

Send 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).

Webhooks

We send a POST with a JSON body to the webhook URL you gave us:

EventWhen
file.completedThe modified file is ready (also after a rework or added options)
file.messageOur tuner wrote a message – included as message
file.cancelledWe cancelled the file (credits refunded)
pingTest 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);

Rate limits

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.

Errors

HTTP 402
{ "error": { "code": "insufficient_credits", "message": "Not enough credits: 4 needed, 1.5 available.", "needed": 4, "balance": 1.5 } }
HTTPcode
401unauthorized – key missing, wrong or deactivated
402insufficient_credits – please top up
404not_found, unknown_endpoint, file_missing
409duplicate_reference (the existing file is returned), not_ready, cannot_cancel, cannot_complain, already_ordered, cannot_change
400invalid_json, invalid_parameter
413file_too_large
422missing_field, missing_file, invalid_file, invalid_stage, invalid_option, invalid_vehicle_type, fuel_mismatch, slave_not_supported, not_offered, mixed_kinds, invalid_kind
429rate_limited – see Rate limits
500server_error – please retry later

Test mode

Ask us for a test key (fsp_test_…) to build and test your integration. It works like a live key, with these differences:

Privacy

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 example

<?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"

Changelog

DateChange
2026-10-24New: 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-21New: 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-14Mercedes CPC Tune: services with "type": "unit", kind = unit – one per file, own file like TCU.
2026-10-11TCU / DSG files: services with "type": "tcu", optional field kind (ecu/tcu), file JSON has kind; errors mixed_kinds, invalid_kind.
2026-10-10Webhooks: 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-09Fields fuel (diesel/petrol) and waiting (delivery time); error fuel_mismatch.
2026-10-07Version 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.