Skip to main content

ADR-006: Bulgarian Vehicle API Integration Strategy

FieldValue
Status✅ Accepted
Date2026-02-25
Author@architect
Deciders@architect, @ivko

Context

Story 020 requires integration with 4 Bulgarian government / semi-official services so club members can check their vehicle obligations without leaving the RCB app:

ServiceDomain
МВР (Ministry of Interior)Unpaid traffic fines
АПИ (Road Infrastructure Agency)Annual road tax (винетка тол)
ГО (Civil Liability Insurance)Third-party liability insurance validity
Виньетка (АППИ Electronic Vignette)Electronic vignette validity

Research Findings

1. МВР — Unpaid Traffic Fines

ItemFinding
Public REST APINone. МВР provides only an HTML form.
Web scrapingLegally risky — МВР prohibits automated access.
DecisionUNAVAILABLE — return a placeholder with a direct link to e-uslugi.mvr.bg.

2. АПИ — Road Tax

ItemFinding
Public REST APIYes — documented at api.bg
AuthenticationRequires merchant/company registration + API key
RequestGET /check?plate={licensePlate}
DecisionIMPLEMENT — Feign client with @CircuitBreaker fallback

3. ГО — Civil Liability Insurance

ItemFinding
Public REST APISemi-official endpoint at gar.bg
AuthenticationNone required for public check endpoint
DecisionIMPLEMENT — Feign client with @CircuitBreaker fallback

4. Виньетка — Electronic Vignette

ItemFinding
Public REST APIYes — АППИ public check service
AuthenticationNone required
DecisionIMPLEMENT — Feign client with @CircuitBreaker fallback

Decision

Implement 3 of 4 checks via OpenFeign clients with Resilience4j circuit breakers:

ServiceImplementation
МВР❌ UNAVAILABLE — hardcoded result with link to e-uslugi.mvr.bg
АПИApiCheckClient Feign interface — returns UNAVAILABLE until merchant credentials added
ГОGoCheckClient Feign interface — circuit breaker fallback to UNAVAILABLE
ВиньеткаVignetteCheckClient Feign interface — circuit breaker fallback to UNAVAILABLE

All 4 checks run in parallel using Java 21 virtual threads (Executors.newVirtualThreadPerTaskExecutor()).

Results are cached 30 minutes per carId via Caffeine (keyed on carId UUID to avoid PII in cache keys).

Membership gate: The service layer checks MembershipService.isMemberActive() before executing any external call. Non-members receive 403 Forbidden.


Consequences

Positive

  • 3 of 4 checks are functional
  • МВР is UNAVAILABLE with a helpful link
  • Circuit breakers prevent cascading failures
  • PII (license plate) is never stored in cache keys

Negative

  • АПИ requires merchant registration before returning live data

Risk

  • ГО endpoint (gar.bg) is undocumented; may break if Guarantee Fund changes its API. Mitigated by circuit breaker + UNAVAILABLE fallback.

References