Developer Documentation

Server-Side Proxy Guide

Use a server-side proxy to keep API Consumer credentials private, request JWT bearer tokens safely, call VINquery APIs, and return clean responses to your applications.

Why a Server-Side Proxy Is Required

API Consumers use a Client ID and Client Secret to request JWT bearer tokens from the VINquery Identity service. The Client Secret is a private credential and must never be sent to browsers, embedded in HTML, committed to public source code, or stored in mobile or desktop applications.

Rule: browser JavaScript, HTML, desktop clients, and mobile apps call your server-side proxy. Your proxy requests tokens and calls VINquery APIs.

The proxy gives you one trusted place to store credentials, cache tokens, normalize errors, log requests, apply rate limits, and keep integrations consistent across your applications.

Recommended Architecture

The recommended flow is:

  1. Client application sends a request to your server-side proxy.
  2. Your proxy obtains or reuses a cached JWT bearer token from https://identity.vinquery.com/connect/token.
  3. Your proxy calls the appropriate VINquery API with Authorization: Bearer <jwt-token>.
  4. Your proxy returns the API response to the client application.
LayerResponsibility
Browser, desktop, or mobile appCollects user input and calls your proxy only.
Server-side proxyStores credentials, obtains tokens, calls VINquery APIs, and returns responses.
VINquery Identity serviceIssues JWT bearer tokens for the requested audience.
VINquery APIsValidate the bearer token and perform the requested service.

Secure Storage of Client ID and Client Secret

Store each API Consumer credential pair in server-side configuration or a secret store. Use a separate API Consumer for each production application, environment, or API audience where operational separation is useful.

Recommended

  • Environment variables
  • Cloud secret stores
  • Server-side encrypted configuration
  • Restricted deployment variables

Avoid

  • Browser JavaScript
  • HTML source
  • Mobile app bundles
  • Public repositories or logs

Requesting, Caching, and Renewing JWT Tokens

Your proxy should request a token from the Identity service only when needed, then cache it server-side until shortly before it expires. Use a short renewal buffer so user requests do not fail when a token is near expiration.

Token Request JSON
{
  "clientId": "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET",
  "audience": "vinquery:api:vindecode"
}

Never return the Client Secret to the client application. In most applications, the JWT token should also remain server-side; the client receives only the VINquery API result or a simplified business response.

Using the Correct JWT Audience

The audience value in the token request must match the Allowed API / JWT Audience configured for that API Consumer in the VINquery portal. A token issued for one audience must not be used to call a different API.

APIAudience
VINdecode APIvinquery:api:vindecode
VINfix APIvinquery:api:vinfix
VINocr APIvinquery:api:vinocr
VINbarcode APIvinquery:api:vinbarcode
DecisioQ APIvinquery:api:decisioq

Calling VINquery APIs Through Your Proxy

After your proxy has a valid JWT, call the target VINquery API with the bearer token in the Authorization header. Keep upstream URLs, credentials, and token responses out of the client application.

API Request
GET https://ws.vinquery.com/vindecode/v3?VIN=1FMCU0F76EUC78242&reportType=3&format=JSON
Authorization: Bearer <jwt-token>

Return the VINquery response as-is when your application needs full API detail, or map it into a simpler response object when your client only needs a small set of fields.

Error Handling

Your proxy should convert low-level authentication, network, and upstream service failures into clear responses for your application. Log enough detail server-side to troubleshoot safely without exposing secrets.

ConditionProxy Behavior
Identity token request failsReturn a clear authentication configuration error and log the upstream status.
VINquery API returns 401 or 403Refresh the token once if appropriate, then return a safe authorization error.
VINquery API returns validation errorForward the service error so the caller can correct the request.
Timeout or network failureReturn a temporary service error and avoid exposing stack traces to the client.

Implementation Examples

These examples show the core proxy pattern: keep credentials on the server, request a JWT token, call a VINquery API, and return the response.

C# / ASP.NET Core

C#
app.MapGet("/api/vindecode", async (string vin, IHttpClientFactory factory, IConfiguration config) =>
{
    var http = factory.CreateClient();
    var tokenResponse = await http.PostAsJsonAsync("https://identity.vinquery.com/connect/token", new
    {
        clientId = config["VINquery:VINdecode:ClientId"],
        clientSecret = config["VINquery:VINdecode:ClientSecret"],
        audience = "vinquery:api:vindecode"
    });
    tokenResponse.EnsureSuccessStatusCode();
    var tokenJson = await tokenResponse.Content.ReadFromJsonAsync<Dictionary<string, string>>();
    var jwt = tokenJson?["jwtToken"];

    var request = new HttpRequestMessage(HttpMethod.Get,
        $"https://ws.vinquery.com/vindecode/v3?VIN={Uri.EscapeDataString(vin)}&reportType=3&format=JSON");
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
    return Results.Content(await (await http.SendAsync(request)).Content.ReadAsStringAsync(), "application/json");
});

Node.js / Express

Node.js
app.get("/api/vindecode", async (req, res) => {
  const token = await fetch("https://identity.vinquery.com/connect/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      clientId: process.env.VINQUERY_CLIENT_ID,
      clientSecret: process.env.VINQUERY_CLIENT_SECRET,
      audience: "vinquery:api:vindecode"
    })
  }).then(r => r.json());

  const api = await fetch(`https://ws.vinquery.com/vindecode/v3?VIN=${encodeURIComponent(req.query.vin)}&reportType=3&format=JSON`, {
    headers: { Authorization: `Bearer ${token.jwtToken}` }
  });
  res.status(api.status).type("application/json").send(await api.text());
});

Python / FastAPI

Python
from fastapi import FastAPI, Response
import os, requests

app = FastAPI()

@app.get("/api/vindecode")
def vindecode(vin: str):
    token = requests.post("https://identity.vinquery.com/connect/token", json={
        "clientId": os.environ["VINQUERY_CLIENT_ID"],
        "clientSecret": os.environ["VINQUERY_CLIENT_SECRET"],
        "audience": "vinquery:api:vindecode"
    }).json()["jwtToken"]

    api = requests.get(
        "https://ws.vinquery.com/vindecode/v3",
        params={"VIN": vin, "reportType": 3, "format": "JSON"},
        headers={"Authorization": f"Bearer {token}"}
    )
    return Response(content=api.text, status_code=api.status_code, media_type="application/json")

PHP

PHP
<?php
$tokenPayload = json_encode([
  "clientId" => getenv("VINQUERY_CLIENT_ID"),
  "clientSecret" => getenv("VINQUERY_CLIENT_SECRET"),
  "audience" => "vinquery:api:vindecode"
]);

$tokenCurl = curl_init("https://identity.vinquery.com/connect/token");
curl_setopt_array($tokenCurl, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
  CURLOPT_POSTFIELDS => $tokenPayload
]);
$token = json_decode(curl_exec($tokenCurl), true)["jwtToken"];
curl_close($tokenCurl);

$vin = urlencode($_GET["vin"] ?? "");
$apiCurl = curl_init("https://ws.vinquery.com/vindecode/v3?VIN=$vin&reportType=3&format=JSON");
curl_setopt_array($apiCurl, [
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer " . $token]
]);
header("Content-Type: application/json");
echo curl_exec($apiCurl);
curl_close($apiCurl);
?>

Java / Spring Boot

Java
@GetMapping("/api/vindecode")
public ResponseEntity<String> vinDecode(@RequestParam String vin) {
    RestClient client = RestClient.create();
    Map<String, String> token = client.post()
        .uri("https://identity.vinquery.com/connect/token")
        .body(Map.of(
            "clientId", System.getenv("VINQUERY_CLIENT_ID"),
            "clientSecret", System.getenv("VINQUERY_CLIENT_SECRET"),
            "audience", "vinquery:api:vindecode"))
        .retrieve()
        .body(Map.class);

    String response = client.get()
        .uri("https://ws.vinquery.com/vindecode/v3?VIN={vin}&reportType=3&format=JSON", vin)
        .header("Authorization", "Bearer " + token.get("jwtToken"))
        .retrieve()
        .body(String.class);
    return ResponseEntity.ok(response);
}

Go

Go
func vinDecodeHandler(w http.ResponseWriter, r *http.Request) {
    payload := []byte(`{"clientId":"` + os.Getenv("VINQUERY_CLIENT_ID") + `","clientSecret":"` + os.Getenv("VINQUERY_CLIENT_SECRET") + `","audience":"vinquery:api:vindecode"}`)
    tokenResp, err := http.Post("https://identity.vinquery.com/connect/token", "application/json", bytes.NewReader(payload))
    if err != nil {
        http.Error(w, "token request failed", http.StatusBadGateway)
        return
    }
    defer tokenResp.Body.Close()

    var token struct { JwtToken string `json:"jwtToken"` }
    json.NewDecoder(tokenResp.Body).Decode(&token)

    url := "https://ws.vinquery.com/vindecode/v3?VIN=" + url.QueryEscape(r.URL.Query().Get("vin")) + "&reportType=3&format=JSON"
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Set("Authorization", "Bearer "+token.JwtToken)
    apiResp, err := http.DefaultClient.Do(req)
    if err != nil {
        http.Error(w, "VINquery request failed", http.StatusBadGateway)
        return
    }
    defer apiResp.Body.Close()
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(apiResp.StatusCode)
    io.Copy(w, apiResp.Body)
}

Security Best Practices and Common Mistakes

Best Practices

  • Use HTTPS for every client-to-proxy request.
  • Keep Client Secrets in server-side secret storage.
  • Cache JWTs server-side and renew before expiration.
  • Log correlation IDs and upstream status codes.
  • Rate-limit and validate client requests before calling VINquery.

Common Mistakes

  • Putting Client Secrets in JavaScript or mobile apps.
  • Using a VINdecode token to call VINfix or another API.
  • Requesting a new token for every client request when caching is possible.
  • Returning stack traces or upstream diagnostics to end users.
  • Logging Client Secrets or raw Authorization headers.

Frequently Asked Questions

Can a browser call VINquery directly?

No. Browser-side code cannot safely hold a Client Secret. Use your server-side proxy.

Can one API Consumer call every VINquery API?

No. Each API Consumer is authorized for one Allowed API / JWT Audience. Create separate API Consumers when your application needs access to multiple API families.

Should my proxy return the JWT to the browser?

Usually no. The safest pattern is for the proxy to keep the token server-side and return only the VINquery API response or the fields your application needs.

Does the proxy need to be platform-specific?

No. The pattern works on Windows, Linux, and macOS. Only deployment setup, environment variable configuration, and secret storage tooling differ by platform.