EPC API

sürüm 1 · https://api.epc.vin/v1 · OpenAPI 3.1

Ne işe yarar

GM, Opel ve Chevrolet parça kataloğunu (epc.vin) kendi yazılımınızdan sorgulamak içindir. Asıl iş, müşterinizin aracının VIN'inden o araca uyan parça numaralarını bulup stoğunuzdakilerle karşılaştırmak.

İki yol var, ikisi de aynı hesaplamadan gelir:

  • POST /parts/check: Elinizdeki numaraları (en çok 500) gönderirsiniz. Her biri için "uyar / uymaz" yanıtı döner; uyuyorsa kataloğun neresinde olduğu da gelir. Az sayıda aday parça için en kolayı budur.
  • GET /vehicles/{vin}/parts: Araca uyan bütün numaraları, alternatifleriyle birlikte alırsınız; karşılaştırmayı kendi sisteminizde yaparsınız. Bütün stoğu taramak için uygundur.

Araç, epc.vin arayüzündeki varsayılan süzgeçlerle değerlendirilir: VIN Süzgeci, UltraVIN ve Yıl Süzgeci açık, parça geçmişi gizli. Arayüzde o aracın sayfalarında görünen her parça API'de de uyar; bu, testlerle doğrulanır. Numaraların alternatifleri, arayüzde numaranın yanındaki G düğmesinin açtığı parça geçmişinin aynısıdır.

Alan adları İngilizce, değerler Türkçedir: açıklamalar, hata ve eşleşme kodları. Yanıtlar UTF-8 JSON'dur; Accept-Encoding: gzip gönderilirse sıkıştırılır.

Hızlı başlangıç

Anahtarınızı bir ortam değişkenine koyun (EPC_API_KEY). Aşağıdaki kurulum, sonraki bütün örneklerde kullanılır. Dili seçtiğinizde sayfadaki bütün örnekler o dile geçer.

export EPC_API_KEY="epc_..."   # yönetimden aldığınız anahtar

curl https://api.epc.vin/v1/usage \
  -H "Authorization: Bearer $EPC_API_KEY"
// .NET 8+. Tek bir HttpClient uygulama boyunca kullanılır.
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var http = new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All })
{
    BaseAddress = new Uri("https://api.epc.vin/v1/"),
    Timeout = TimeSpan.FromSeconds(60),
};
http.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("EPC_API_KEY"));

// Alan adları snake_case: part_numbers ↔ PartNumbers
var json = new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
    PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
};

// Hata yanıtı: { "error": { "code": "...", "message": "..." } }
async Task<T> Read<T>(HttpResponseMessage res)
{
    if (res.IsSuccessStatusCode) return (await res.Content.ReadFromJsonAsync<T>(json))!;
    var err = await res.Content.ReadFromJsonAsync<ErrorAnswer>(json);
    var wait = res.Headers.RetryAfter?.Delta;   // 429'da kaç saniye beklenmeli
    throw new HttpRequestException($"{(int)res.StatusCode} {err?.Error.Code}: {err?.Error.Message}", null, res.StatusCode);
}

var usage = await Read<JsonElement>(await http.GetAsync("usage"));
Console.WriteLine(usage);

record ErrorAnswer(ErrorBody Error);
record ErrorBody(string Code, string Message);
// Node.js 18+ (fetch yerleşik, sıkıştırmayı kendisi açar).
const API = 'https://api.epc.vin/v1';
const KEY = process.env.EPC_API_KEY;

async function epc(path, body) {
  const res = await fetch(API + path, {
    method: body ? 'POST' : 'GET',
    headers: {
      Authorization: `Bearer ${KEY}`,
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json();
  if (!res.ok) {
    // { "error": { "code": "...", "message": "..." } }
    const err = new Error(`${res.status} ${data.error.code}: ${data.error.message}`);
    err.code = data.error.code;
    err.retryAfter = Number(res.headers.get('retry-after')) || undefined;   // 429'da saniye
    throw err;
  }
  return data;
}

console.log(await epc('/usage'));
# Python 3.9+, requests
import os
import requests

API = "https://api.epc.vin/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['EPC_API_KEY']}"

def epc(path, body=None):
    if body is None:
        res = session.get(API + path, timeout=60)
    else:
        res = session.post(API + path, json=body, timeout=60)
    if not res.ok:
        # { "error": { "code": "...", "message": "..." } }
        error = res.json()["error"]
        raise RuntimeError(f"{res.status_code} {error['code']}: {error['message']} "
                           f"(Retry-After: {res.headers.get('Retry-After')})")
    return res.json()

print(epc("/usage"))

Makine okunur tanım: /v1/openapi.json. Postman, Insomnia ya da NSwag, openapi-generator gibi kod üreticilere doğrudan verilebilir.

Anahtar ve güvenlik

Her istekte Authorization: Bearer <anahtar> başlığı gönderilir. Anahtarlar epc_ ile başlar ve 53 karakterdir; epc.vin yönetimi verir. Anahtar yalnız verildiği anda bir kez gösterilir. Biz yalnız özetini saklarız; anahtar kaybolursa yenisi verilir.

  • Anahtarı yalnız sunucunuzda tutun: bir ortam değişkeninde ya da sır deposunda. Tarayıcıda, mobil uygulamada ya da kaynak kodda tutmayın. API tarayıcıdan çağrılamaz, çünkü CORS izni yoktur.
  • Her sisteminiz için ayrı anahtar isteyin (ör. "stok sunucusu", "test"). Biri sızarsa yalnız o iptal edilir.
  • Anahtarı değiştirirken kesinti olmasın diye önce yenisini alıp sisteminize koyun, sonra eskisini iptal ettirin.
  • Yalnız HTTPS kullanılır. Yanıtlar önbelleğe alınmaz (Cache-Control: no-store).
  • Her yanıtta bir X-Request-Id başlığı döner. Bir sorunu bildirirken bu kimliği de iletin.

Bir adresten 15 dakikada 30 hatalı anahtar denenirse o adres bir süre bekletilir (cok_fazla_hatali_anahtar).

Sınırlar

SınırVarsayılanAşılınca
Hesap başına dakikada istek60429 istek_siniri
Günde farklı VIN (İstanbul saatiyle; aynı gün aynı VIN yeniden sayılmaz)500429 gunluk_vin_kotasi
Bir kontrolde parça numarası500400 cok_fazla_numara
İstek gövdesi64 KB413 govde_cok_buyuk

Sınırlar hesaba özeldir; ihtiyacınız farklıysa yönetimle konuşun. Her yanıtta dakika sınırının durumu, IETF taslağındaki başlıklarla gelir:

RateLimit-Policy: "dakika";q=60;w=60
RateLimit: "dakika";r=57;t=41      # bu dakikadan 57 istek kaldı, 41 sn sonra yenilenir

429 yanıtlarında Retry-After kaç saniye bekleneceğini söyler. O kadar bekleyip yeniden deneyin; art arda denemeyin.

Hatalar

Hatalar her uçta aynı biçimdedir. code programlar için sabittir; message insanlar içindir ve değişebilir.

{
  "error": {
    "code": "gecersiz_vin",
    "message": "VIN 17 karakter olmalı; harf ve rakam, I, O ve Q yok."
  }
}
DurumKodAnlamı
400gecersiz_vinVIN 17 karakter değil ya da I, O, Q içeriyor
400gecersiz_numaraParça numarası harf ve rakam dışında karakter içeriyor ya da 32 karakterden uzun
400gecersiz_istekGövde ya da sorgu okunamadı, tanınmayan bir alan var
400numara_yokpart_numbers boş
400cok_fazla_numara500'den fazla numara
401anahtar_yokAuthorization başlığı yok
401gecersiz_anahtarAnahtar tanınmıyor
401anahtar_iptalAnahtar iptal edilmiş
403hesap_kapaliAPI hesabı kapalı ya da süresi doldu
404arac_bulunamadiVIN geçerli ama katalogda aracı yok
404donanim_bulunamadiVerilen equipment_id bu VIN'in araçlarından biri değil
404parca_bulunamadiParça numarası katalogda yok
404bulunamadiBöyle bir adres yok
405yontem_desteklenmiyorAdres bu yöntemle çağrılmaz (ör. GET yerine POST)
413govde_cok_buyukGövde 64 KB'tan büyük
415json_bekleniyorContent-Type: application/json değil
429istek_siniriDakika sınırı doldu
429gunluk_vin_kotasiGünlük VIN kotası doldu (bugün sorulan VIN'ler yine sorulabilir)
429cok_fazla_hatali_anahtarBu adresten çok fazla hatalı anahtar denendi
500sunucu_hatasiBizim tarafta bir hata; X-Request-Id ile bildirin

VIN'in aracı

GET/vehicles/{vin}VIN kotasına sayılır

VIN'i çözer. Bir VIN bazen birden çok donanıma denk gelir; o durumda diğer uçlara equipment_id verilir, verilmezse ilk araç kullanılır.

İstek

curl https://api.epc.vin/v1/vehicles/KL1SF48W9BB261734 \
  -H "Authorization: Bearer $EPC_API_KEY"
var answer = await Read<VehicleAnswer>(await http.GetAsync("vehicles/KL1SF48W9BB261734"));
foreach (var v in answer.Vehicles)
    Console.WriteLine($"{v.EquipmentId}: {v.Make} {v.Model} ({v.Source})");

record VehicleAnswer(string Vin, List<Vehicle> Vehicles);
record Vehicle(int EquipmentId, string? Make, string? Model, string? CatalogCode, string? CatalogName,
               string? EquipmentName, string? ProductionDate, string Source);
const { vehicles } = await epc('/vehicles/KL1SF48W9BB261734');
for (const v of vehicles) console.log(`${v.equipment_id}: ${v.make} ${v.model} (${v.source})`);
for v in epc("/vehicles/KL1SF48W9BB261734")["vehicles"]:
    print(v["equipment_id"], v["make"], v["model"], v["source"])

Yanıt 200

{
  "vin": "KL1SF48W9BB261734",
  "vehicles": [
    {
      "equipment_id": 2986,
      "make": "Chevrolet",
      "model": "AVEO (T250 / T255) [EUR] (2006 - 2011)",
      "catalog_code": "T06",
      "catalog_name": "T06",
      "equipment_name": "AVEO (T250/T255) [EUR]",
      "production_date": null,
      "source": "arac_kaydi"
    }
  ]
}

source, aracın nereden bulunduğunu söyler. arac_kaydi: VIN katalogun araç kayıtlarında var; motor, şanzıman ve seçenekler gibi donanım bilgisi biliniyor. vin_cozumleme: VIN kayıtlarda yok, VIN'in kendisinden çözüldü. Bu araçların donanımı daha az bilindiği için uyan parça listesi daha geniştir; bir parçayı satmadan önce kullanım bilgisine (usage) bakın.

Parça kontrolü

POST/parts/checkVIN kotasına sayılır

Gönderdiğiniz parça numaralarının araca uyup uymadığını söyler. Her numara gönderildiği sırayla ve gönderildiği yazımla yanıtlanır.

AlanTür
vinmetinZorunlu. Küçük harf de olur.
part_numbersmetin dizisiZorunlu, 1 ile 500 arası. GM parça numarası, Opel KAT numarası ya da tireli numaralar (08361-15063).
equipment_idsayıİsteğe bağlı. VIN birden çok donanıma denk geliyorsa hangisi.

Karşılaştırmada iki tarafta da boşluk, tire, nokta ve eğik çizgi atılır, harfler büyütülür: 13 300 900, 13-300-900 ve 13300900 aynıdır; 0836115063, katalogdaki 08361-15063'ü bulur.

İstek

curl https://api.epc.vin/v1/parts/check \
  -H "Authorization: Bearer $EPC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vin": "KL1SF48W9BB261734",
    "part_numbers": ["96829637", "95955933", "13300900", "abc;"]
  }'
var check = await Read<CheckAnswer>(await http.PostAsJsonAsync("parts/check", new
{
    vin = "KL1SF48W9BB261734",
    part_numbers = new[] { "96829637", "95955933", "13300900", "abc;" },
}, json));

foreach (var r in check.Results)
{
    if (r.Fits)
        Console.WriteLine($"{r.PartNumber}: uyar ({r.Match}) → {string.Join(" › ", r.Places![0].Group)}");
    else
        Console.WriteLine($"{r.PartNumber}: uymaz ({r.Reason})");
}

record CheckAnswer(string Vin, Vehicle Vehicle, CheckSummary Summary, List<CheckResult> Results);
record CheckSummary(int Asked, int Fits);
record CheckResult(string PartNumber, bool Fits, string? Match, List<string>? CatalogPartNumbers,
                   List<Place>? Places, List<string>? Markets, string? Reason);
record Place(List<string> Group, string? PageCode, string? PageTitle, string? Callout, string? Description,
             string? Usage, string? Quantity, List<string> Notes, bool NotServiced, string? CatalogPartNumber);
const { results } = await epc('/parts/check', {
  vin: 'KL1SF48W9BB261734',
  part_numbers: ['96829637', '95955933', '13300900', 'abc;'],
});

for (const r of results) {
  if (r.fits) console.log(`${r.part_number}: uyar (${r.match}) → ${r.places[0].group.join(' › ')}`);
  else console.log(`${r.part_number}: uymaz (${r.reason})`);
}
answer = epc("/parts/check", {
    "vin": "KL1SF48W9BB261734",
    "part_numbers": ["96829637", "95955933", "13300900", "abc;"],
})
for r in answer["results"]:
    if r["fits"]:
        print(r["part_number"], "uyar", r["match"], " › ".join(r["places"][0]["group"]))
    else:
        print(r["part_number"], "uymaz", r["reason"])

Yanıt 200

{
  "vin": "KL1SF48W9BB261734",
  "vehicle": {
    "equipment_id": 2986,
    "make": "Chevrolet",
    "model": "AVEO (T250 / T255) [EUR] (2006 - 2011)",
    "catalog_code": "T06",
    "catalog_name": "T06",
    "equipment_name": "AVEO (T250/T255) [EUR]",
    "production_date": null,
    "source": "arac_kaydi"
  },
  "summary": { "asked": 4, "fits": 2 },
  "results": [
    {
      "part_number": "96829637",
      "fits": true,
      "match": "birebir",
      "catalog_part_numbers": ["96829637"],
      "places": [
        {
          "group": ["YAKIT & MOTOR KONTROL", "YAKIT"],
          "page_code": "2111",
          "page_title": "YAKIT DEPOSU II  (2111)",
          "callout": "1",
          "description": "DEPO A-YAKIT 45L",
          "usage": "(BASE,LS,LT,E) (69,08,48) (&LDT/LMU)",
          "quantity": "1",
          "notes": [],
          "not_serviced": false,
          "catalog_part_number": "96829637"
        }
      ]
    },
    {
      "part_number": "95955933",
      "fits": true,
      "match": "yeni_numara",
      "catalog_part_numbers": ["96829637"],
      "places": [ { "group": ["YAKIT & MOTOR KONTROL", "YAKIT"], "callout": "1", "...": "..." } ],
      "markets": ["GM Daewoo", "Tayland", "Meksika"]
    },
    { "part_number": "13300900", "fits": false, "reason": "araca_uymuyor" },
    { "part_number": "abc;", "fits": false, "reason": "gecersiz_numara" }
  ]
}
Alan
matchUyuyorsa nasıl uyduğu: birebir, yeni_numara, eski_numara (ayrıntı).
catalog_part_numbersKatalogda bu araç için listelenen numara(lar). Yeni/eski numarayla uyuyorsa sizin gönderdiğinizden farklıdır.
placesKataloğun neresinde olduğu: gruplar (üstten alta), sayfa, çizimdeki numara (callout), açıklama, kullanım, adet. Bir numaranın en çok 20 yeri döner.
marketsYalnız yeni/eski numara eşleşmesinde: iki numarayı bağlayan fiyat kitaplarının pazarı. Bizim pazarımız GM Avrupa ve GM Daewoo'dur.
reasonUymuyorsa nedeni: araca_uymuyor (numara katalogda var ama bu araca değil), katalogda_yok, gecersiz_numara.

Araca uyan tüm parçalar

GET/vehicles/{vin}/parts?equipment_id=VIN kotasına sayılır

Araca uyan her parça numarası bir kez döner; sıra katalog sırasıdır, grup grup ve sayfa sayfa. Her numara yerleri ve alternatifleriyle gelir. Örnek ölçümler:

AraçNumaraAlternatif (ek)Aranabilecek toplamSıkıştırılmış
Kayıtlı (arac_kaydi), Aveo T2501.7761.8463.622~65 KB
VIN'den çözülen (vin_cozumleme), Astra-F11.8278.26020.087~625 KB (ham 9 MB)

Bir aracın ilk listesi genellikle 100–300 ms sürer (sunucu yeni açıldıysa ve katalog henüz hazırlanmadıysa birkaç saniye); aynı aracın sonraki istekleri 30 dakika boyunca birkaç milisaniyede döner. Accept-Encoding: gzip göndermeyi unutmayın; çoğu HTTP kütüphanesi bunu kendiliğinden yapar.

İstek

curl --compressed https://api.epc.vin/v1/vehicles/KL1SF48W9BB261734/parts \
  -H "Authorization: Bearer $EPC_API_KEY"
var list = await Read<PartsList>(await http.GetAsync("vehicles/KL1SF48W9BB261734/parts"));
Console.WriteLine($"{list.Vehicle.Model}: {list.Count} parça");

record PartsList(string Vin, Vehicle Vehicle, int Count, List<Part> Parts);
record Part(string PartNumber, string? Description, List<string> ReplacedBy,
            List<Alternative> Alternatives, List<Place> Places);
record Alternative(string PartNumber, string Relation, bool Interchangeable, List<string> Markets);
const list = await epc('/vehicles/KL1SF48W9BB261734/parts');
console.log(`${list.vehicle.model}: ${list.count} parça`);
parts = epc("/vehicles/KL1SF48W9BB261734/parts")
print(parts["vehicle"]["model"], parts["count"], "parça")

Yanıt 200

{
  "vin": "KL1SF48W9BB261734",
  "vehicle": { "equipment_id": 2986, "make": "Chevrolet", "source": "arac_kaydi", "...": "..." },
  "count": 1776,
  "parts": [
    {
      "part_number": "96829637",
      "description": "DEPO A-YAKIT 45L",
      "replaced_by": ["95955933"],
      "alternatives": [
        {
          "part_number": "95955933",
          "relation": "yeni_numara",
          "interchangeable": true,
          "markets": ["GM Daewoo", "Tayland", "Meksika"]
        },
        {
          "part_number": "96537108",
          "relation": "eski_numara",
          "interchangeable": false,
          "markets": ["Tayland"]
        }
      ],
      "places": [
        {
          "group": ["YAKIT & MOTOR KONTROL", "YAKIT"],
          "page_code": "2111",
          "page_title": "YAKIT DEPOSU II  (2111)",
          "callout": "1",
          "description": "DEPO A-YAKIT 45L",
          "usage": "(BASE,LS,LT,E) (69,08,48) (&LDT/LMU)",
          "quantity": "1",
          "notes": [],
          "not_serviced": false
        }
      ]
    }
  ]
}
Alan
replaced_byBizim pazarımızın kitaplarında (GM Avrupa, GM Daewoo) numaranın yerine geçen numaralar; zincir sırasıyla gelir, en yenisi sondadır.
alternativesBütün pazarların kitaplarında numarayla bağlı her numara. Bunlar arayüzdeki "G" penceresinin içeriğidir; önce bizim pazarımızınkiler gelir (ayrıntı).
placesNumaranın kataloğun bu araç için gösterdiği her yeri.

Parça geçmişi

GET/parts/{part_number}VIN kotasına sayılmaz

Numaranın geçmişini verir: bizim pazarımızın kitaplarında yerine geçen ve yerini aldığı numaralar ile bütün pazarlardaki alternatifleri. Stok kartlarınızı güncel numaraya eşlemek için kullanılır. Numara tireli de tiresiz de yazılabilir; yanıt, numarayı katalogun yazdığı biçimde verir.

İstek

curl https://api.epc.vin/v1/parts/96829637 \
  -H "Authorization: Bearer $EPC_API_KEY"
var history = await Read<PartHistory>(await http.GetAsync("parts/96829637"));
var current = history.ReplacedBy.LastOrDefault() ?? history.PartNumber;   // bugünkü numara

record PartHistory(string PartNumber, List<string> ReplacedBy, List<string> Replaces,
                   List<Alternative> Alternatives);
const history = await epc('/parts/96829637');
const current = history.replaced_by.at(-1) ?? history.part_number;   // bugünkü numara
history = epc("/parts/96829637")
current = (history["replaced_by"] or [history["part_number"]])[-1]   # bugünkü numara

Yanıt 200

{
  "part_number": "96829637",
  "replaced_by": ["95955933"],
  "replaces": [],
  "alternatives": [
    {
      "part_number": "95955933",
      "relation": "yeni_numara",
      "interchangeable": true,
      "markets": ["GM Daewoo", "Tayland", "Meksika"]
    },
    {
      "part_number": "96537108",
      "relation": "eski_numara",
      "interchangeable": false,
      "markets": ["Tayland"]
    }
  ]
}

Kullanım

GET/usage

Hesabın sınırlarını ve bugünkü kullanımını verir (İstanbul saatiyle).

İstek

curl https://api.epc.vin/v1/usage \
  -H "Authorization: Bearer $EPC_API_KEY"
var usage = await Read<UsageAnswer>(await http.GetAsync("usage"));
Console.WriteLine($"Bugün {usage.Today.Vins}/{usage.Limits.VinsPerDay} VIN");

record UsageAnswer(string Account, Limits Limits, Today Today);
record Limits(int RequestsPerMinute, int VinsPerDay, int PartNumbersPerCheck);
record Today(int Vins, int Requests);
const { limits, today } = await epc('/usage');
console.log(`Bugün ${today.vins}/${limits.vins_per_day} VIN`);
usage = epc("/usage")
print(f"Bugün {usage['today']['vins']}/{usage['limits']['vins_per_day']} VIN")

Yanıt 200

{
  "account": "yedekparca",
  "limits": {
    "requests_per_minute": 60,
    "vins_per_day": 500,
    "part_numbers_per_check": 500
  },
  "today": {
    "vins": 12,
    "requests": 40
  }
}

Eşleşme ve alternatifler

Kontrolde bir numara üç yoldan uyar; en iyisi seçilir:

birebirNumara (ya da KAT numarası), aracın kataloğunda araca uyan bir satırda geçiyor.
yeni_numaraKatalog eski bir numarayı listeliyor; gönderdiğiniz numara onun yerine geçen yenisi. Genellikle stokta tutulması gereken budur.
eski_numaraKatalog yeni numarayı listeliyor; gönderdiğiniz numara onun yerini aldığı eskisi. Stoktaki eski parça çoğu zaman kullanılabilir, ama satmadan önce kontrol edin.

Alternatifler, GM'in pazar pazar tuttuğu fiyat kitaplarındaki parça geçmişidir; arayüzde numaranın yanındaki "G" düğmesi aynı bilgiyi gösterir. Her alternatifte şunlar bulunur:

  • relation: yeni_numara (bu numaranın yerine geçer) ya da eski_numara (bu numaranın yerini aldığı).
  • interchangeable: kitap ikisini birbirinin yerine kullanılabilir diye işaretliyorsa true; yalnız biri ötekinin yerine geçiyorsa (tek yön) false.
  • markets: bağı kuran kitapların pazarı: GM Avrupa, GM Daewoo, ABD, Kanada, Meksika, Brezilya, Avustralya, Tayland, Çin, Hindistan… Bizim pazarımızınkiler (GM Avrupa, GM Daewoo) en güvenilir olanlardır; başka pazarların bağı o pazarın parçası içindir, satmadan önce kontrol edin.

Kontrol, alternatiflerin hepsini hesaba katar. Uyan sonuçta bağı kuran pazarlar markets alanında gelir.

Stokla karşılaştırma

Müşteri tezgâhta, VIN elinizde: stokta olan ve araca uyan parçaları göstermek için iki yol.

Az sayıda aday: kontrol

Stoktaki numaraları 500'erli parçalar hâlinde gönderin, uyanları alın.

async Task<List<CheckResult>> Fitting(string vin, IEnumerable<string> stock)
{
    var fitting = new List<CheckResult>();
    foreach (var chunk in stock.Chunk(500))
    {
        var answer = await Read<CheckAnswer>(await http.PostAsJsonAsync("parts/check",
            new { vin, part_numbers = chunk }, json));
        fitting.AddRange(answer.Results.Where(r => r.Fits));
    }
    return fitting;
}

foreach (var r in await Fitting("KL1SF48W9BB261734", new[] { "96829637", "95955933", "13300900" }))
    Console.WriteLine($"{r.PartNumber} {r.Match} {r.Places![0].Description}");
async function fitting(vin, stock) {
  const out = [];
  for (let i = 0; i < stock.length; i += 500) {
    const { results } = await epc('/parts/check', { vin, part_numbers: stock.slice(i, i + 500) });
    out.push(...results.filter((r) => r.fits));
  }
  return out;
}

for (const r of await fitting('KL1SF48W9BB261734', ['96829637', '95955933', '13300900'])) {
  console.log(r.part_number, r.match, r.places[0].description);
}
def fitting(vin, stock):
    out = []
    for i in range(0, len(stock), 500):
        answer = epc("/parts/check", {"vin": vin, "part_numbers": stock[i:i + 500]})
        out += [r for r in answer["results"] if r["fits"]]
    return out

for r in fitting("KL1SF48W9BB261734", ["96829637", "95955933", "13300900"]):
    print(r["part_number"], r["match"], r["places"][0]["description"])

Bütün stok: liste

Aracın listesini alın; katalogdaki numarayı ve alternatiflerini stoğunuzda arayın. Numaraları iki tarafta da aynı biçime getirin (boşluk, tire, nokta ve eğik çizgi atılmış, büyük harf).

static string Compact(string n) => new string(n.Where(char.IsLetterOrDigit).ToArray()).ToUpperInvariant();

// stock: numara → adet
async Task<List<(string Number, string? Description, int Count)>> InStock(string vin, Dictionary<string, int> stock)
{
    var have = stock.ToDictionary(s => Compact(s.Key), s => s.Value);
    var list = await Read<PartsList>(await http.GetAsync($"vehicles/{vin}/parts"));
    var found = new List<(string, string?, int)>();
    foreach (var p in list.Parts)
    {
        // Katalogdaki numara, sonra alternatifleri (önce bizim pazarımızınkiler).
        var candidates = new[] { p.PartNumber }.Concat(p.Alternatives.Select(a => a.PartNumber));
        var hit = candidates.FirstOrDefault(n => have.GetValueOrDefault(Compact(n)) > 0);
        if (hit != null) found.Add((hit, p.Description, have[Compact(hit)]));
    }
    return found;
}
const compact = (n) => n.replace(/[^0-9a-z]/gi, '').toUpperCase();

// stock: { "96829637": 3, ... } (numara → adet)
async function inStock(vin, stock) {
  const have = new Map(Object.entries(stock).map(([n, count]) => [compact(n), count]));
  const { parts } = await epc(`/vehicles/${vin}/parts`);
  const found = [];
  for (const p of parts) {
    // Katalogdaki numara, sonra alternatifleri (önce bizim pazarımızınkiler).
    const hit = [p.part_number, ...p.alternatives.map((a) => a.part_number)].find((n) => have.get(compact(n)) > 0);
    if (hit) found.push({ number: hit, description: p.description, count: have.get(compact(hit)) });
  }
  return found;
}
def compact(n):
    return "".join(ch for ch in n if ch.isalnum()).upper()

def in_stock(vin, stock):  # stock: {"96829637": 3, ...} (numara → adet)
    have = {compact(n): count for n, count in stock.items()}
    for p in epc(f"/vehicles/{vin}/parts")["parts"]:
        # Katalogdaki numara, sonra alternatifleri (önce bizim pazarımızınkiler).
        for n in [p["part_number"], *(a["part_number"] for a in p["alternatives"])]:
            if have.get(compact(n)):
                yield n, p["description"], have[compact(n)]
                break

Aynı müşteri için art arda sorgu atmayın; aracın listesini kendi tarafınızda bir süre saklayabilirsiniz, katalog verisi değişmez.

Notlar ve sürümler

  • Sürüm yolu /v1. Geriye uyumlu eklemeler (yeni alan, yeni uç) bu sürümde yapılır; tanımadığınız alanları görmezden gelin. Uyumu bozan bir değişiklik olursa /v2 açılır ve /v1 bir süre daha çalışır.
  • Değerlerin Türkçe olması şimdilik böyledir; kodlar (birebir, araca_uymuyor…) sabittir.
  • Bu belge sunucuyla birlikte yayımlanır: gördüğünüz belge her zaman çalışan API'nin belgesidir.

2026-10 · v1.0 ilk sürüm: araç, parça kontrolü, tüm parçalar, parça geçmişi, alternatifler, kullanım.