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.
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:
- Client application sends a request to your server-side proxy.
- Your proxy obtains or reuses a cached JWT bearer token from
https://identity.vinquery.com/connect/token. - Your proxy calls the appropriate VINquery API with
Authorization: Bearer <jwt-token>. - Your proxy returns the API response to the client application.
| Layer | Responsibility |
|---|---|
| Browser, desktop, or mobile app | Collects user input and calls your proxy only. |
| Server-side proxy | Stores credentials, obtains tokens, calls VINquery APIs, and returns responses. |
| VINquery Identity service | Issues JWT bearer tokens for the requested audience. |
| VINquery APIs | Validate 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.
{
"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.
| API | Audience |
|---|---|
| VINdecode API | vinquery:api:vindecode |
| VINfix API | vinquery:api:vinfix |
| VINocr API | vinquery:api:vinocr |
| VINbarcode API | vinquery:api:vinbarcode |
| DecisioQ API | vinquery: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.
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.
| Condition | Proxy Behavior |
|---|---|
| Identity token request fails | Return a clear authentication configuration error and log the upstream status. |
| VINquery API returns 401 or 403 | Refresh the token once if appropriate, then return a safe authorization error. |
| VINquery API returns validation error | Forward the service error so the caller can correct the request. |
| Timeout or network failure | Return 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
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
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
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
$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
@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
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.
