LapViz API guides
Everything you need to use the LapViz API from a script, a server or a data notebook. For the exact parameters and schemas of each operation, see the API reference.
Overview
The LapViz API is a REST API over HTTPS. It reads and writes the lap timing sessions of lapviz.com: their laps, sectors and telemetry channels, the imports of data logger files, the teams and the circuits. It is the API the LapViz apps use.
- Base URL:
https://lapviz.com/api - Format: JSON (UTF-8) in and out, except file uploads (
multipart/form-data) and file downloads. - Authentication: a personal API key in the
X-Api-Keyheader. - Description: OpenAPI 3, to generate clients and import into Postman, Insomnia or Bruno.
Quickstart
- Sign in and create a key in Account › API keys. Copy it: it is shown only once.
- Keep it in an environment variable, never in your code.
- Ask the API who you are.
# Shell
export LAPVIZ_API_KEY="lvz_..."
curl -H "X-Api-Key: $LAPVIZ_API_KEY" https://lapviz.com/api/users/me
# Python 3 (pip install requests)
import os
import requests
api = requests.Session()
api.headers["X-Api-Key"] = os.environ["LAPVIZ_API_KEY"]
api.headers["User-Agent"] = "my-lapviz-tool/1.0"
me = api.get("https://lapviz.com/api/users/me")
me.raise_for_status()
print(me.json()["driverInfo"]["displayName"])
// JavaScript (Node.js 18+, on a server: never in a web page)
const response = await fetch("https://lapviz.com/api/users/me", {
headers: { "X-Api-Key": process.env.LAPVIZ_API_KEY },
});
if (!response.ok) throw new Error(`LapViz API: ${response.status}`);
console.log(await response.json());
// C# (.NET 8+)
using var http = new HttpClient { BaseAddress = new Uri("https://lapviz.com/api/") };
http.DefaultRequestHeaders.Add("X-Api-Key", Environment.GetEnvironmentVariable("LAPVIZ_API_KEY"));
var me = await http.GetFromJsonAsync<JsonElement>("users/me");
Console.WriteLine(me.GetProperty("email"));
Try it without a key
Public data needs no key: the circuits, and the sessions their drivers made public. Paste these in a terminal or a browser.
# Circuits around a position, and their timing lines
curl "https://lapviz.com/api/circuit/nearby?lat=50.97&lon=5.47&radius=50"
curl "https://lapviz.com/api/circuit/configurations?lastUpdateDateTime=2000-01-01T00:00:00Z&lat=50.97&lon=5.47&radius=50"
Public example sessions, with their telemetry, one per circuit:
| sessionId | Circuit | Driver | Date | Best lap |
|---|---|---|---|---|
7e2b5e |
Karting des Fagnes | 28 Sep 2024 | 0:54.816 | |
c24e7c |
Genk | 3 Feb 2024 | 1:09.581 | |
b8d718 |
Karting Zuera | 29 Feb 2024 | 1:03.810 | |
ca4d00 |
Karting Le Mans | 3 Oct 2020 | 0:56.345 | |
d59931 |
Karting Adria | 16 Nov 2019 | 0:53.160 | |
018018 |
Karting Rivesaltes | 13 Feb 2020 | 0:58.552 |
# Laps, sectors and channels, then every channel of lap 2
curl https://lapviz.com/api/session/7e2b5e
curl https://lapviz.com/api/session/alldata/7e2b5e/2
To try the imports, the example files of the LapViz format can be sent to your own account. In the API reference, the public operations work without Authorize.
Authentication
Every request that needs an account carries a personal API key in the X-Api-Key header. A key starts
with lvz_ and acts in the name of the account that created it, with what that account may see.
Keys are only accepted in the header: a key in a URL would end up in logs and browser histories.
- Create, list and revoke your keys in Account › API keys, up to 10 active keys. LapViz only stores a hash of each key: a lost key cannot be shown again, create a new one.
- A revoked key stops working immediately.
- A missing, unknown, expired or revoked key gets
401 Unauthorized. Some operations also work anonymously (public sessions, circuits): without a key they answer what a visitor of the site sees.
The LapViz apps sign in with short-lived access tokens (Authorization: Bearer). That flow is reserved for the LapViz apps: integrations use API keys and never ask for a user's password.
Scopes and expiration
Give each key the smallest power it needs.
| Scope | May | May not |
|---|---|---|
read |
GET requests, and the searches sent as POST (/api/session/search, /api/session/import/detect). |
Anything that changes data: 403 with the title Read-only API key. |
write |
Everything a read key may, plus import, edit and delete sessions and manage teams. | With any key: delete the account, change its e-mail address or profile, create API keys. Those stay on the site and in the apps. |
A key can expire after 7, 30, 90 or 365 days, or never. Prefer a key that expires: if it leaks, the damage stops by itself. Create the next key before the current one expires, deploy it, then revoke the old one.
Requests and responses
- JSON names are in camelCase. Unknown fields may be added to the responses at any time: ignore what you do not know.
- Instants are ISO 8601 with an offset:
2026-10-18T14:03:12.5+02:00. Send UTC (Z) in your filters. - Durations (lap and sector times) are written
hh:mm:ss.fffffff:00:00:49.8120000is 49.812 seconds. - Sessions are identified by their share identifier, e.g.
9e8451, the end of the address of the session page (https://lapviz.com/session/9e8451). - Accounts are identified like
users/1-A. In a path, encode the slash:/api/users/info/users%2F1-A. - Telemetry samples use short names to keep large responses small:
{ "s": 12.34, "d": 87.5 }is the value 87.5 at 12.34 seconds from the start of the first lap. - Responses are compressed (gzip, Brotli) when your client accepts it: most HTTP libraries do it for you.
- Identify your tool in the
User-Agentheader, e.g.my-dashboard/2.1 (you@example.com): it helps us contact you rather than block you if something goes wrong.
What a key can see
A key sees exactly what its owner sees on the site, nothing more. For a session, that depends on how its driver shared it:
| Sharing | Lap times | Telemetry and exports |
|---|---|---|
Public | Everybody | Everybody |
Laptimes | Everybody | The driver, the people it is shared with, his coaches |
Restricted | The driver, the people it is shared with, his coaches | The same |
Private | The driver, his coaches | The driver, his coaches |
The coaches are the coaches of a team the driver chose to share his sessions with. A session you may not see answers 404 Not Found, exactly like a session that does not exist: the API never reveals that a private session exists.
Errors
Errors use the standard HTTP status codes. Most bodies are RFC 9457 problem details (application/problem+json):
{
"type": "https://lapviz.com/developers/docs#scopes",
"title": "Read-only API key",
"status": 403,
"detail": "This API key has the read scope: it cannot change data. ..."
}
A few validation errors carry a plain message instead (for example "Invalid cursor"): show it, do not parse it.
| Status | Meaning | What to do |
|---|---|---|
400 | The request is invalid: a field, a format, a header. | Fix the request; the body says what is wrong. |
401 | No key, or a key that is unknown, expired or revoked. | Check the header; create a new key if needed. |
403 | Authenticated, but not allowed: a read-only key, or an action reserved to a team admin or owner. | Use a key with the write scope, or ask the right person. |
404 | Not found, or not visible to you. | Check the identifier and the sharing of the session. |
410 | Gone: the session you are uploading again was deleted. | Do not retry; it was deleted on purpose. |
429 | Too many requests. | Wait Retry-After seconds, then retry. |
5xx | A problem on our side. | Retry later with an increasing delay; tell us if it lasts. |
Rate limits
| Who | Limit |
|---|---|
| An account (any key or token) | 600 requests per minute |
| Anonymous calls, per IP address | 300 requests per minute |
| All calls from one IP address | 1,200 requests per minute |
| Team invitation codes tried, per account | 20 per 10 minutes |
Every API response tells you where you stand:
RateLimit-Policy: 600;w=60 RateLimit-Limit: 600 RateLimit-Remaining: 587
Over the limit, the API answers 429 Too Many Requests with a Retry-After header in seconds. Slow down when RateLimit-Remaining gets low, and cache what does not change: the samples of a session never change once imported.
Need more for a legitimate use? Tell us about your project.
Pagination
Pages: the session search
POST /api/session/search returns one page at a time: send activePageIndex (from 1) and pageSize (at most 20); totalRecords counts every page. The newest sessions come first.
curl -X POST https://lapviz.com/api/session/search \
-H "X-Api-Key: $LAPVIZ_API_KEY" -H "Content-Type: application/json" \
-d '{ "mine": true, "circuitCode": "mariembourg", "activePageIndex": 1, "pageSize": 20 }'
Cursor: the change feed
GET /api/session/changes returns what changed in your library since a cursor. Keep the cursor of each response for the next call, and call again right away while hasMore is true. Cursors do not expire.
Recipe: read the telemetry of a session
Find a session, read its laps and channels, and load one lap into a data frame.
import os import requests import pandas as pd API = "https://lapviz.com/api" api = requests.Session() api.headers["X-Api-Key"] = os.environ["LAPVIZ_API_KEY"] # Your latest session with telemetry found = api.post(f"{API}/session/search", json={"mine": True, "telemetryDataAvailable": True, "pageSize": 1}).json() session_id = found["sessions"][0]["shareId"] # Laps, sectors and channel names session = api.get(f"{API}/session/{session_id}").json() print(session["best"], session["channels"]) # Every channel of lap 3: { "GPS Speed": [ { "s": seconds, "d": value }, ... ], "RPM": [...], ... } channels = api.get(f"{API}/session/alldata/{session_id}/3").json()["channelData"] name = "GPS Speed" if "GPS Speed" in channels else "Speed" speed = pd.DataFrame(channels[name]).rename(columns={"s": "seconds", "d": "km_h"}) print(speed.describe())
For one channel only, GET /api/session/data/{sessionId}/{lap}/{channel} is lighter. Channel names depend on the data logger: read them from channels.
Recipe: import a file
Upload the file of a data logger or lap timer app; LapViz recognises the format (more than 20), creates the sessions in the background and gives you a job to follow. Needs a write key.
curl -H "X-Api-Key: $LAPVIZ_API_KEY" \
-H "lv-session-id: my-tool-2026-10-18-001" \
-F "file=@session.xrk" \
https://lapviz.com/api/session/import
# → { "jobId": "48213" }
import time
with open("session.xrk", "rb") as file:
job = api.post(f"{API}/session/import", files={"file": file}).json()
while True:
response = api.get(f"{API}/session/import/{job['jobId']}")
status = response.json() if response.status_code == 200 else {"state": "Unknown"}
if status["state"] in ("Success", "Failed", "Deleted"):
break
time.sleep(2)
print(status["state"], status.get("shareIds"), status.get("errors"))
lv-session-id(optional, your own identifier: letters, digits and dashes, up to 64) makes the upload idempotent: sending the same session again returnssessionIdinstead of creating a duplicate, and a session deleted meanwhile answers410.- A file can hold several sessions:
shareIdslists them all. - The sessions take the default sharing of the account; change it with
PATCH /api/session/{sessionId}. - Files are limited to 20 MB.
Recipe: keep a copy of a library
The change feed and the LapViz files let you mirror your library incrementally, like the LapViz apps do.
import json, pathlib
state = pathlib.Path("cursor.txt")
cursor = state.read_text() if state.exists() else None
while True:
page = api.get(f"{API}/session/changes", params={"cursor": cursor, "pageSize": 200}).json()
for summary in page["sessions"]: # created or changed
lvz = api.get(f"{API}/session/{summary['shareId']}/file")
pathlib.Path(f"{summary['shareId']}.lvz").write_bytes(lvz.content)
pathlib.Path(f"{summary['shareId']}.json").write_text(json.dumps(summary))
for gone in page["deleted"]: # deleted or given to another account
pathlib.Path(f"{gone['shareId']}.lvz").unlink(missing_ok=True)
cursor = page["cursor"] or cursor
if not page["hasMore"]:
break
if cursor:
state.write_text(cursor)
The .lvz file is the open LapViz format: a ZIP with a JSON manifest and the samples in columns, read by LapViz.Telemetry. Downloads can be resumed with Range requests; send If-Range with the ETag, which changes when the session is edited.
Recipe: export a session
GET /api/session/export/{sessionId}/{format} converts a session for other tools:
| Format | For |
|---|---|
lvz | The LapViz format: every sample and every timing source. |
csv | Spreadsheets, pandas, R. |
gpx, kml, geojson | Maps and GIS tools: the trajectory. |
srt, csrt | Video editors: subtitles, plain or coloured, with the lap times. |
mgjson | Adobe After Effects motion graphics (data-driven overlays). |
txt | A text description of the session. |
org | The original file that was imported (its owner only). |
curl -H "X-Api-Key: $LAPVIZ_API_KEY" -o session.gpx https://lapviz.com/api/session/export/9e8451/gpx
Every format carries the telemetry, so exports follow the telemetry rules of What a key can see.
Recipe: circuits for a lap timer
The circuit operations are public. GET /api/circuit/configurations returns the start/finish and sector lines of the circuits around a position as CircuitConfiguration objects of LapViz.Telemetry, ready for its lap timer service.
curl "https://lapviz.com/api/circuit/configurations?lastUpdateDateTime=2000-01-01T00:00:00Z&lat=50.0855&lon=4.4931&radius=50"
// C#: time laps from GPS fixes with the circuits of LapViz (dotnet add package LapViz.Telemetry)
var circuits = await http.GetFromJsonAsync<List<CircuitConfiguration>>(
"circuit/configurations?lastUpdateDateTime=2000-01-01T00:00:00Z&lat=50.0855&lon=4.4931&radius=50");
Send the time of your previous call as lastUpdateDateTime to receive only the circuits that changed. To find a circuit by name, use GET /api/circuit/search?text=genk; for the nearest ones, GET /api/circuit/nearby?lat=…&lon=….
Webhooks
Rather than asking the API again and again whether something changed, let LapViz tell your server. A webhook posts the
events of your account to an HTTPS address of yours, a few seconds after they happen. Create one in
Account › Webhooks or with POST /api/webhooks, up to 5 per account.
| Event | When | data |
|---|---|---|
session.created | A session of your account was imported (one event per session of a file). | sessionId, clientSessionId, circuitId, start, best, sharing, uploadedBy, url |
session.updated | Its label, description, notes, sharing, vehicle, setup or video changed. | sessionId, clientSessionId, changed (the fields), url |
session.deleted | A session of your account was deleted. | sessionId, clientSessionId, deleted |
import.failed | A file you sent could not be imported. | jobId, fileName, errorCode, errors, existingSessionId |
ping | On demand, to try your server. | webhookId, message |
The events are small on purpose: read the details with the API (GET /api/session/{sessionId}), with what your key may see. Every request is a POST of JSON:
POST /lapviz/webhook HTTP/1.1
Content-Type: application/json
User-Agent: LapViz-Webhooks/1.0 (+https://lapviz.com/developers/docs#webhooks)
LapViz-Event: session.created
LapViz-Event-Id: evt_6f1c2a...
LapViz-Delivery: 42-A
LapViz-Signature: t=1760788992,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"id": "evt_6f1c2a...",
"type": "session.created",
"created": "2026-10-18T14:03:12.0000000+00:00",
"data": {
"sessionId": "9e8451",
"start": "2026-10-18T13:40:05+02:00",
"best": "00:00:49.8120000",
"sharing": "Private",
"url": "https://lapviz.com/session/9e8451"
}
}
Answer quickly
Answer with any 2xx status within 10 seconds, then do the work. Any other answer, a timeout or a refused connection is a failure: the event is sent again after 1 minute, 5 minutes, 30 minutes, 2, 5 and 10 hours (7 attempts over about 18 hours). After 10 events in a row that failed every attempt, the webhook is disabled and you get a notification; resume it once your server is fixed. Redirects are not followed.
Check the signature
Anyone can post to your address: check that the request comes from LapViz. LapViz-Signature holds a timestamp
t and v1, the HMAC-SHA256 in hexadecimal of {t}.{body} with the secret of the webhook
(whsec_..., shown once when you create it or roll it). Compute it on the raw body, before any JSON parsing,
compare in constant time, and refuse a timestamp older than 5 minutes.
# Python (Flask)
import hashlib, hmac, os, time
from flask import Flask, request, abort
SECRET = os.environ["LAPVIZ_WEBHOOK_SECRET"].encode()
app = Flask(__name__)
@app.post("/lapviz/webhook")
def lapviz_webhook():
parts = dict(p.split("=", 1) for p in request.headers.get("LapViz-Signature", "").split(",") if "=" in p)
body = request.get_data() # The raw bytes
expected = hmac.new(SECRET, parts.get("t", "").encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, parts.get("v1", "")) or abs(time.time() - int(parts.get("t", 0))) > 300:
abort(400)
event = request.get_json()
print(event["type"], event["data"])
return "", 204
// Node.js (Express)
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/lapviz/webhook", express.raw({ type: "application/json" }), (req, res) => {
const parts = Object.fromEntries(req.get("LapViz-Signature").split(",").map((p) => p.split("=")));
const expected = crypto.createHmac("sha256", process.env.LAPVIZ_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`).digest("hex");
const valid = parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!valid || Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return res.sendStatus(400);
const event = JSON.parse(req.body);
console.log(event.type, event.data);
res.sendStatus(204);
});
// C# (ASP.NET Core minimal API)
app.MapPost("/lapviz/webhook", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var body = await reader.ReadToEndAsync();
var parts = request.Headers["LapViz-Signature"].ToString().Split(',')
.Select(p => p.Split('=', 2)).Where(p => p.Length == 2).ToDictionary(p => p[0], p => p[1]);
var secret = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("LAPVIZ_WEBHOOK_SECRET")!);
var expected = Convert.ToHexString(HMACSHA256.HashData(secret, Encoding.UTF8.GetBytes($"{parts.GetValueOrDefault("t")}.{body}"))).ToLowerInvariant();
var fresh = long.TryParse(parts.GetValueOrDefault("t"), out var t) && Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) <= 300;
if (!fresh || !CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(expected), Encoding.ASCII.GetBytes(parts.GetValueOrDefault("v1") ?? "")))
return Results.BadRequest();
var lapvizEvent = JsonDocument.Parse(body).RootElement;
Console.WriteLine(lapvizEvent.GetProperty("type"));
return Results.NoContent();
});
Good practice
- Deduplicate on
id: an event may arrive twice (a retry after a timeout, a redelivery). Process eachidonce. - Do not rely on the order: a retried event may arrive after a newer one. Read the current state with the API when the order matters.
- Keep the secret secret, like an API key. Rolling it (New secret, or
POST /api/webhooks/{id}/secret) replaces it at once. - The deliveries of the last 30 days, with their payload and the answer of your server, are listed on the webhook page and by
GET /api/webhooks/{id}/deliveries; send one again with Send again. - For a server on your laptop during development, expose it with a tunnel (Cloudflare Tunnel, ngrok...): LapViz only posts to public HTTPS addresses.
MCP: your sessions in AI assistants
LapViz is an MCP server: connect Claude, Cursor, VS Code or any
MCP client to https://lapviz.com/mcp with an API key, and ask in plain words: "Where did I lose time in my last session at Genk?",
"Which setup was fastest in the wet?", "How far am I from the circuit record?". It is the toolbox of the LapViz coach.
| Tool | What it reads |
|---|---|
search_sessions | Your sessions, and optionally those you may see; their ids for the other tools. |
get_session | A session: lap and sector times, ideal lap, consistency, degradation, personal best, rank, setup, weather. |
compare_laps | Where the time goes between two laps: time per corner, entry, apex and exit speeds, braking distance. |
get_comparison_targets | Laps worth comparing with: personal bests, circuit record, the other drivers of the group. |
get_leaderboard | The public leaderboard of a circuit, records per category, your place. |
get_circuit | A circuit: location, named corners and their guide, your best lap. |
get_progress | Your progress per circuit, month by month. |
get_setups | Your setups on a circuit and the best one per conditions. |
The tools only read, with the rights of your key: a read key is enough, and recommended. They return summaries, never the raw telemetry, so that a question stays small. Each call counts in the rate limit of your account.
# Claude Code
claude mcp add --transport http lapviz https://lapviz.com/mcp --header "X-Api-Key: $LAPVIZ_API_KEY"
// Cursor: .cursor/mcp.json
{
"mcpServers": {
"lapviz": { "url": "https://lapviz.com/mcp", "headers": { "X-Api-Key": "lvz_..." } }
}
}
// VS Code: .vscode/mcp.json
{
"inputs": [{ "id": "lapviz-key", "type": "promptString", "description": "LapViz API key", "password": true }],
"servers": {
"lapviz": { "type": "http", "url": "https://lapviz.com/mcp", "headers": { "X-Api-Key": "${input:lapviz-key}" } }
}
}
// Claude Desktop: claude_desktop_config.json (through the mcp-remote bridge)
{
"mcpServers": {
"lapviz": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://lapviz.com/mcp", "--header", "X-Api-Key:${LAPVIZ_API_KEY}"],
"env": { "LAPVIZ_API_KEY": "lvz_..." }
}
}
}
Transport: Streamable HTTP, stateless. What the tools return goes to the AI service you connect, under its own terms: connect only the services you trust with your sessions.
Sign in with LapViz (OAuth)
An API key gives access to its owner's account: perfect for your own scripts. To act for other LapViz users (a coaching app, a dashboard, a connector), let them sign in with LapViz: they approve your application on lapviz.com, you get tokens for their account, and they can disconnect you at any time in Account › Apps. LapViz follows OAuth 2.1: authorization code with PKCE (S256, required), short access tokens, rotating refresh tokens.
| Endpoint | |
|---|---|
GET /.well-known/oauth-authorization-server | Metadata (RFC 8414): every endpoint below. |
GET /oauth/authorize | The consent page the user is sent to. |
POST /oauth/token | Code or refresh token for tokens (form encoded). |
POST /oauth/revoke | Revocation (RFC 7009): revoking the refresh token disconnects the application. |
POST /oauth/register | Dynamic client registration (RFC 7591), public clients only. |
1. Register your application
In Account › Apps: a name shown to the users, the redirect addresses (https, or http on localhost), and whether it is confidential (it runs on a server and keeps a client secret) or public (a browser or mobile app: no secret, PKCE protects the code). You get a client_id (lvzc_...) and, for a confidential app, a secret shown once.
2. Send the user to the consent page
https://lapviz.com/oauth/authorize?response_type=code &client_id=lvzc_... &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback &scope=read &state=RANDOM_PER_REQUEST &code_challenge=BASE64URL_SHA256_OF_THE_VERIFIER &code_challenge_method=S256
scope is read or write (see scopes). The user signs in if needed and approves; LapViz redirects to your address with code, your state (check it) and iss, or with error=access_denied. The code is valid 2 minutes, once.
3. Exchange the code
curl -X POST https://lapviz.com/oauth/token \ -d grant_type=authorization_code -d code=THE_CODE \ -d redirect_uri=https://app.example.com/callback \ -d code_verifier=THE_VERIFIER \ -d client_id=lvzc_... -d client_secret=lvzcs_... # the secret: confidential apps only (or HTTP Basic) # → { "access_token": "lvzat_...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "lvzrt_...", "scope": "read" }
4. Call the API
curl -H "Authorization: Bearer lvzat_..." https://lapviz.com/api/users/me
The access token lasts an hour. Renew it with grant_type=refresh_token: you get a new refresh token each time; keep only the latest. A refresh token used twice reveals a theft: LapViz then ends the whole authorization, and the user must approve again. The authorization also ends when the user disconnects your app, signs out everywhere or changes his password.
Tokens act as an ordinary user, whatever the account: never with administrator rights, never able to delete the account or change its e-mail address. Keep the secret and the refresh tokens on your server.
AI connectors (claude.ai, ChatGPT)
The MCP server answers 401 with WWW-Authenticate: Bearer resource_metadata=... (RFC 9728): an MCP client that supports OAuth discovers LapViz, registers itself and opens the consent page. Add a custom connector with the address https://lapviz.com/mcp; no key to copy.
Client libraries
.NET: LapViz.Client
The official client, MIT licensed, for .NET Standard 2.0 (.NET Framework 4.6.2+, .NET 6+, MAUI): sessions, telemetry, imports, the change feed, teams, webhooks, and LapVizWebhook.Verify to check the webhooks you receive.
dotnet add package LapViz.Client
var lapviz = new LapVizClient(new LapVizClientOptions { ApiKey = Environment.GetEnvironmentVariable("LAPVIZ_API_KEY") });
var page = await lapviz.Sessions.SearchAsync(new SessionSearchRequestDto { Mine = true, PageSize = 20, ActivePageIndex = 1 });
Python: lapviz
Standard library only, Python 3.10+, MIT licensed: the same operations, verify_signature for your webhook receivers, and typed errors.
pip install lapviz
from lapviz import LapViz
lapviz = LapViz() # reads LAPVIZ_API_KEY
for session in lapviz.sessions.iterate(mine=True):
print(session["shareId"], session["best"])
TypeScript and JavaScript: lapviz
No dependency, ES module, typed from the OpenAPI description; Node 20+, Deno and Bun. verifySignature uses Web Crypto, so it also runs in Cloudflare Workers.
npm install lapviz
import { LapViz } from "lapviz";
const lapviz = new LapViz(); // reads LAPVIZ_API_KEY
for await (const session of lapviz.sessions.iterate({ mine: true })) {
console.log(session.shareId, session.best);
}
Postman, Bruno, Insomnia
Download the Postman collection: every operation of the reference, grouped like it, with sample bodies. Bruno and Insomnia import it too. Set the collection variable apiKey to your key; baseUrl is already https://lapviz.com. The collection is built from the OpenAPI description: download it again to get the new operations.
Other languages
For other languages, the OpenAPI description generates a typed client:
# Go, Java, Ruby, PHP, Rust... with OpenAPI Generator npx @openapitools/openapi-generator-cli generate \ -i https://lapviz.com/swagger/v1/swagger.json -g go -o lapviz-client # C#, TypeScript, Python, Go... with Kiota kiota generate -l CSharp -d https://lapviz.com/swagger/v1/swagger.json -c LapVizClient -n LapViz.Api -o ./LapVizApi
For timing itself (laps from GPS, sectors, predictive delta, file formats), use the open source LapViz.Telemetry library for .NET. Built a client for your language? Share it on Discord: we list community libraries.
Versioning and changes
- This is version 1 of the API. Within it, changes are additive: new operations, new optional parameters, new fields in the responses.
- Removing or changing an operation or a field is announced on this page and on Discord at least 6 months ahead.
- Operations missing from the API reference are internal to the LapViz apps and may change without notice: do not rely on them.
Changelog
Follow the changes with the RSS feed.
- 18 October 2026: Python and TypeScript SDKs. Official clients for Python (pip install lapviz) and TypeScript/JavaScript (npm install lapviz), MIT licensed and without dependencies: sessions, telemetry, imports, change feed, teams, webhooks, circuits and the webhook signature check.
- 18 October 2026: Postman collection. Every operation of the reference as a Postman collection, which Bruno and Insomnia import too: /developers/lapviz.postman_collection.json. Built from the OpenAPI description, so it is always complete.
- 18 October 2026: Sign in with LapViz (OAuth 2.1). Applications act for the users who authorize them, without sharing a key: authorization code with PKCE, refresh token rotation, revocation, dynamic client registration. The MCP server announces it, so claude.ai and ChatGPT connectors connect by themselves.
- 18 October 2026: MCP server. lapviz.com is an MCP server: connect Claude, Cursor, VS Code or any MCP client to /mcp with an API key and ask about your sessions in plain words. The tools are the read tools of the LapViz coach.
- 18 October 2026: LapViz.Client 1.0 for .NET. The official .NET client, MIT licensed: sessions, telemetry, imports, change feed, teams, webhooks, and LapVizWebhook.Verify for your receivers.
- 18 October 2026: Webhooks. Your server is told when a session is imported, changed or deleted, or when an import fails. Signed with HMAC-SHA256, retried for about 18 hours, managed in Account › Webhooks and with /api/webhooks.
- 18 October 2026: Developer portal, API keys with scopes. Guides and reference; API keys with a read or write scope and an expiration; RateLimit headers on every response; the circuit operations in the public reference; GET /api/users/me returns the account only.
Status
GET https://lapviz.com/health answers Healthy when the API and its database work, without authentication: point your monitoring at it. Planned maintenance and incidents are announced on Discord.
Security
Keep your keys safe
- Store keys in environment variables or a secret manager; never commit them. If a key ever reaches a repository, revoke it, even after deleting the commit.
- Call the API from a server or a script, never from a web page or a mobile app: anybody could read the key there.
- One key per tool or machine, named after it, with the
readscope when it does not change data and an expiration. - Check Last used in Account › API keys, and revoke the keys you no longer use.
Responsible disclosure
Found a vulnerability? Write to support@lapviz.com with the subject Security before telling anybody else, and give us a reasonable time to fix it. Test only with your own account and your own data; never access, change or delete the data of others. We answer every report and credit the researchers who wish it.
Fair use
The API is free for drivers, coaches, teams and developers who build on their own data or on data shared with them. In return:
- Respect the choices of the drivers: do not republish a session outside of LapViz beyond what its sharing allows.
- Do not harvest public sessions or profiles in bulk, and do not resell LapViz data.
- Stay within the rate limits and cache what does not change.
- Say that the data comes from LapViz when you show it, with a link to the session when you can.
Use of the API is subject to the terms of use of LapViz. We may suspend a key that harms the service or its users, and we will tell you why.