├── .browserslistrc ├── .gitignore ├── dist ├── request.js ├── account.js ├── video.js ├── athena.js ├── config.js ├── derived.js ├── index.js ├── auth.js ├── clips.js ├── navigation.js ├── billing.js ├── raw.js ├── devices.js ├── instance.js └── drives.js ├── src ├── account.js ├── request.js ├── config.js ├── derived.js ├── video.js ├── athena.js ├── index.js ├── auth.js ├── clips.js ├── billing.js ├── navigation.js ├── raw.js ├── drives.js ├── devices.js └── instance.js ├── .dockerignore ├── babel.config.json ├── README.md ├── Dockerfile ├── nginx.conf ├── .eslintrc.cjs ├── LICENSE ├── CHANGELOG.md ├── package.json ├── .github └── workflows │ ├── build.yaml │ └── docs.yaml ├── index.d.ts └── openapi.yaml /.browserslistrc: -------------------------------------------------------------------------------- 1 | last 1 year 2 | > 0.25% 3 | not dead 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | 4 | .idea 5 | *.iml 6 | .vscode 7 | *.swp 8 | 9 | index.html 10 | -------------------------------------------------------------------------------- /dist/request.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | export default new ConfigRequest(Config.COMMA_URL_ROOT); -------------------------------------------------------------------------------- /src/account.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export function getProfile(dongleId = 'me') { 5 | return request.get(`v1/${dongleId}/`); 6 | } 7 | -------------------------------------------------------------------------------- /src/request.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | 4 | 5 | export default new ConfigRequest(Config.COMMA_URL_ROOT); 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .github 2 | dist 3 | node_modules 4 | 5 | .vscode 6 | .idea 7 | *.iml 8 | *.swp 9 | 10 | .DS_Store 11 | 12 | .gitignore 13 | *.html 14 | *.log 15 | -------------------------------------------------------------------------------- /dist/account.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | export function getProfile() { 3 | let dongleId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'me'; 4 | return request.get(`v1/${dongleId}/`); 5 | } -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "@babel/plugin-transform-runtime" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | COMMA_URL_ROOT: window?.COMMA_URL_ROOT || 'https://api.comma.ai/', 3 | ATHENA_URL_ROOT: window?.ATHENA_URL_ROOT || 'https://athena.comma.ai/', 4 | BILLING_URL_ROOT: window?.BILLING_URL_ROOT || 'https://billing.comma.ai/', 5 | }; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @commaai/api 2 | 3 | [comma API](https://api.comma.ai) client library 4 | 5 | ## Installation 6 | 7 | You can install the package using the GitHub URL. It is recommended to use a specific version tag. 8 | 9 | ``` 10 | # add to your package.json dependencies 11 | 12 | "@commaai/api": "commaai/comma-api#v3.0.1", 13 | ``` 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-bullseye AS build 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json yarn.lock /app/ 6 | RUN yarn install --immutable --immutable-cache --check-cache 7 | 8 | COPY . /app/ 9 | RUN yarn docs 10 | 11 | 12 | FROM nginx:1.22 13 | 14 | COPY --from=build /app/index.html /app/openapi.yaml /usr/share/nginx/html/ 15 | COPY nginx.conf /etc/nginx/conf.d/default.conf 16 | -------------------------------------------------------------------------------- /dist/video.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | import Config from './config'; 3 | import ConfigRequest from './instance'; 4 | const request = new ConfigRequest(Config.COMMA_URL_ROOT); 5 | export function getQcameraStreamUrl(routeStr, exp, sig) { 6 | const query = qs.stringify({ 7 | exp, 8 | sig 9 | }); 10 | return `${request.baseUrl}v1/route/${routeStr}/qcamera.m3u8?${query}`; 11 | } -------------------------------------------------------------------------------- /src/derived.js: -------------------------------------------------------------------------------- 1 | import ConfigRequest from './instance'; 2 | 3 | 4 | export default function routeApi(routeSigUrl) { 5 | const request = new ConfigRequest(routeSigUrl); 6 | 7 | return { 8 | getCoords: async (cacheKey = 0) => request.get(`route.coords?s=${cacheKey}`), 9 | getJpegUrl: (routeOffsetSeconds) => `${routeSigUrl}/sec/${routeOffsetSeconds.toString()}.jpg`, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen [::]:80; 4 | server_name localhost; 5 | 6 | gzip on; 7 | gzip_types text/html text/plain text/css text/xml text/javascript application/javascript application/x-javascript; 8 | gzip_min_length 1024; 9 | gzip_vary on; 10 | 11 | root /usr/share/nginx/html; 12 | location / { 13 | try_files $uri $uri/ /index.html; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/video.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | 3 | import Config from './config'; 4 | import ConfigRequest from './instance'; 5 | 6 | 7 | const request = new ConfigRequest(Config.COMMA_URL_ROOT); 8 | 9 | export function getQcameraStreamUrl(routeStr, exp, sig) { 10 | const query = qs.stringify({ exp, sig }); 11 | return `${request.baseUrl}v1/route/${routeStr}/qcamera.m3u8?${query}`; 12 | } 13 | -------------------------------------------------------------------------------- /src/athena.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | 4 | 5 | const request = new ConfigRequest(Config.ATHENA_URL_ROOT); 6 | 7 | export function configure(accessToken, errorResponseCallback = null) { 8 | request.configure(accessToken, errorResponseCallback); 9 | } 10 | 11 | export async function postJsonRpcPayload(dongleId, payload) { 12 | return request.post(dongleId, payload); 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es2021: true, 6 | }, 7 | extends: 'airbnb-base', 8 | parserOptions: { 9 | ecmaVersion: 'latest', 10 | sourceType: 'module', 11 | }, 12 | rules: { 13 | camelcase: 'off', 14 | 'import/prefer-default-export': 'off', 15 | 'max-classes-per-file': 'off', 16 | 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1 }], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /dist/athena.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | const request = new ConfigRequest(Config.ATHENA_URL_ROOT); 4 | export function configure(accessToken) { 5 | let errorResponseCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 6 | request.configure(accessToken, errorResponseCallback); 7 | } 8 | export async function postJsonRpcPayload(dongleId, payload) { 9 | return request.post(dongleId, payload); 10 | } -------------------------------------------------------------------------------- /dist/config.js: -------------------------------------------------------------------------------- 1 | var _window, _window2, _window3; 2 | export default { 3 | COMMA_URL_ROOT: ((_window = window) === null || _window === void 0 ? void 0 : _window.COMMA_URL_ROOT) || 'https://api.comma.ai/', 4 | ATHENA_URL_ROOT: ((_window2 = window) === null || _window2 === void 0 ? void 0 : _window2.ATHENA_URL_ROOT) || 'https://athena.comma.ai/', 5 | BILLING_URL_ROOT: ((_window3 = window) === null || _window3 === void 0 ? void 0 : _window3.BILLING_URL_ROOT) || 'https://billing.comma.ai/' 6 | }; -------------------------------------------------------------------------------- /dist/derived.js: -------------------------------------------------------------------------------- 1 | import ConfigRequest from './instance'; 2 | export default function routeApi(routeSigUrl) { 3 | const request = new ConfigRequest(routeSigUrl); 4 | return { 5 | getCoords: async function () { 6 | let cacheKey = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0; 7 | return request.get(`route.coords?s=${cacheKey}`); 8 | }, 9 | getJpegUrl: routeOffsetSeconds => `${routeSigUrl}/sec/${routeOffsetSeconds.toString()}.jpg` 10 | }; 11 | } -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | import * as account from './account'; 2 | import * as athena from './athena'; 3 | import * as auth from './auth'; 4 | import * as billing from './billing'; 5 | import * as clips from './clips'; 6 | import * as devices from './devices'; 7 | import derived from './derived'; 8 | import * as drives from './drives'; 9 | import * as raw from './raw'; 10 | import request from './request'; 11 | import * as navigation from './navigation'; 12 | import * as video from './video'; 13 | export { account, athena, auth, billing, clips, devices, derived, drives, raw, request, navigation, video }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as account from './account'; 2 | import * as athena from './athena'; 3 | import * as auth from './auth'; 4 | import * as billing from './billing'; 5 | import * as clips from './clips'; 6 | import * as devices from './devices'; 7 | import derived from './derived'; 8 | import * as drives from './drives'; 9 | import * as raw from './raw'; 10 | import request from './request'; 11 | import * as navigation from './navigation'; 12 | import * as video from './video'; 13 | 14 | 15 | export { 16 | account, 17 | athena, 18 | auth, 19 | billing, 20 | clips, 21 | devices, 22 | derived, 23 | drives, 24 | raw, 25 | request, 26 | navigation, 27 | video, 28 | }; 29 | -------------------------------------------------------------------------------- /src/auth.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export async function refreshAccessToken(code, provider) { 5 | const resp = await request.postForm('v2/auth/', { code, provider }); 6 | 7 | if (resp.access_token != null) { 8 | request.configure(resp.access_token); 9 | return resp.access_token; 10 | } if (resp.response !== undefined) { 11 | throw new Error(`Could not exchange oauth code for access token: response ${resp.response}`); 12 | } else if (resp.error !== undefined) { 13 | throw new Error(`Could not exchange oauth code for access token: error ${resp.error}`); 14 | } else { 15 | throw new Error(`Could not exchange oauth code for access token: ${resp}`); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /dist/auth.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | export async function refreshAccessToken(code, provider) { 3 | const resp = await request.postForm('v2/auth/', { 4 | code, 5 | provider 6 | }); 7 | if (resp.access_token != null) { 8 | request.configure(resp.access_token); 9 | return resp.access_token; 10 | } 11 | if (resp.response !== undefined) { 12 | throw new Error(`Could not exchange oauth code for access token: response ${resp.response}`); 13 | } else if (resp.error !== undefined) { 14 | throw new Error(`Could not exchange oauth code for access token: error ${resp.error}`); 15 | } else { 16 | throw new Error(`Could not exchange oauth code for access token: ${resp}`); 17 | } 18 | } -------------------------------------------------------------------------------- /src/clips.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export async function clipsCreate(route, title, start_time, end_time, video_type, is_public) { 5 | return request.post('v1/clips/create', { 6 | route, title, start_time, end_time, video_type, is_public, 7 | }); 8 | } 9 | 10 | export async function clipsList(dongle_id) { 11 | return request.get('v1/clips/list', { dongle_id }); 12 | } 13 | 14 | export async function clipsDetails(dongle_id, clip_id) { 15 | return request.get('v1/clips/details', { dongle_id, clip_id }); 16 | } 17 | 18 | export async function clipsUpdate(dongle_id, clip_id, is_public) { 19 | return request.patch('v1/clips/update', { dongle_id, clip_id, is_public }); 20 | } 21 | 22 | export async function clipsDelete(dongle_id, clip_id) { 23 | return request.delete('v1/clips/update', { dongle_id, clip_id }); 24 | } 25 | -------------------------------------------------------------------------------- /dist/clips.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | export async function clipsCreate(route, title, start_time, end_time, video_type, is_public) { 3 | return request.post('v1/clips/create', { 4 | route, 5 | title, 6 | start_time, 7 | end_time, 8 | video_type, 9 | is_public 10 | }); 11 | } 12 | export async function clipsList(dongle_id) { 13 | return request.get('v1/clips/list', { 14 | dongle_id 15 | }); 16 | } 17 | export async function clipsDetails(dongle_id, clip_id) { 18 | return request.get('v1/clips/details', { 19 | dongle_id, 20 | clip_id 21 | }); 22 | } 23 | export async function clipsUpdate(dongle_id, clip_id, is_public) { 24 | return request.patch('v1/clips/update', { 25 | dongle_id, 26 | clip_id, 27 | is_public 28 | }); 29 | } 30 | export async function clipsDelete(dongle_id, clip_id) { 31 | return request.delete('v1/clips/update', { 32 | dongle_id, 33 | clip_id 34 | }); 35 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Comma.ai, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 3.2.0 2 | ============= 3 | * breaking: remove deprecated fetchRoutes 4 | 5 | Version 3.1.3 6 | ============= 7 | * add `route_str` parameter to `/v1/devices//routes_segments` 8 | 9 | Version 3.1.2 10 | ============= 11 | * add `limit` parameter to `/v1/devices//routes_segments` 12 | 13 | Version 3.1.1 14 | ============= 15 | * add error response callback 16 | * revert package name to @commaai/api 17 | 18 | Version 3.1.0 19 | ============= 20 | * rename package to @comma/api 21 | * create OpenAPI spec for the API 22 | * generate types using openapi-typescript 23 | 24 | Version 3.0.1 25 | ============= 26 | * fix: disable transformation of ES module syntax 27 | * see https://babeljs.io/docs/en/babel-preset-env#modules 28 | * ci: check that dist folder is correct 29 | 30 | Version 3.0.0 31 | ============= 32 | * breaking: rename package to @commaai/api and deprecate from npm 33 | * install the package using git url instead 34 | * breaking: upgrade to babel 7 35 | * use browserslist to set target browsers 36 | * breaking: remove annotation type validation code 37 | * breaking: remove leaderboard 38 | * add eslint 39 | -------------------------------------------------------------------------------- /src/billing.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | 4 | 5 | const request = new ConfigRequest(Config.BILLING_URL_ROOT); 6 | 7 | export function configure(accessToken, errorResponseCallback = null) { 8 | request.configure(accessToken, errorResponseCallback); 9 | } 10 | 11 | export async function getSubscription(dongle_id) { 12 | return request.get('v1/prime/subscription', { dongle_id }); 13 | } 14 | 15 | export async function getSubscribeInfo(dongle_id) { 16 | return request.get('v1/prime/subscribe_info', { dongle_id }); 17 | } 18 | 19 | export async function cancelPrime(dongle_id) { 20 | return request.post('v1/prime/cancel', { dongle_id }); 21 | } 22 | 23 | export async function getStripeCheckout(dongle_id, sim_id, plan) { 24 | return request.post('v1/prime/stripe_checkout', { dongle_id, sim_id, plan }); 25 | } 26 | 27 | export async function getStripePortal(dongle_id) { 28 | return request.get('v1/prime/stripe_portal', { dongle_id }); 29 | } 30 | 31 | export async function getStripeSession(dongle_id, session_id) { 32 | return request.get('v1/prime/stripe_session', { dongle_id, session_id }); 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@commaai/api", 3 | "version": "3.2.0", 4 | "main": "src/index.js", 5 | "browser": "dist/index.js", 6 | "types": "index.d.ts", 7 | "repository": "https://github.com/commaai/comma-api.git", 8 | "homepage": "https://api.comma.ai", 9 | "license": "MIT", 10 | "scripts": { 11 | "generate": "openapi-typescript ./openapi.yaml --output ./index.d.ts", 12 | "build": "babel --out-dir ./dist ./src", 13 | "lint": "eslint ./src", 14 | "lint:fix": "eslint --fix ./src", 15 | "serve": "redocly preview-docs ./openapi.yaml", 16 | "docs": "redocly build-docs --cdn ./openapi.yaml --output index.html" 17 | }, 18 | "dependencies": { 19 | "@babel/runtime": "^7.20.0", 20 | "query-string": "^7.0.1" 21 | }, 22 | "devDependencies": { 23 | "@babel/cli": "^7.19.3", 24 | "@babel/core": "^7.19.6", 25 | "@babel/plugin-transform-runtime": "^7.19.6", 26 | "@babel/preset-env": "^7.19.4", 27 | "@redocly/cli": "^1.0.0-beta.114", 28 | "eslint": "^8.26.0", 29 | "eslint-config-airbnb-base": "^15.0.0", 30 | "eslint-plugin-import": "^2.26.0", 31 | "openapi-typescript": "^6.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/navigation.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | export function setDestination(dongleId, latitude, longitude, place_name, place_details) { 3 | return request.post(`v1/navigation/${dongleId}/set_destination`, { 4 | latitude, 5 | longitude, 6 | place_name, 7 | place_details 8 | }); 9 | } 10 | export function getLocationsData(dongleId) { 11 | return request.get(`v1/navigation/${dongleId}/locations`); 12 | } 13 | export function putLocationSave(dongleId, latitude, longitude, place_name, place_details, save_type, label) { 14 | return request.put(`v1/navigation/${dongleId}/locations`, { 15 | latitude, 16 | longitude, 17 | place_name, 18 | place_details, 19 | save_type, 20 | label 21 | }); 22 | } 23 | export function patchLocationSave(dongleId, navLocationId, saveType, label) { 24 | return request.patch(`v1/navigation/${dongleId}/locations`, { 25 | id: navLocationId, 26 | save_type: saveType, 27 | label 28 | }); 29 | } 30 | export function deleteLocationSave(dongleId, navLocationId) { 31 | return request.delete(`v1/navigation/${dongleId}/locations`, { 32 | id: navLocationId 33 | }); 34 | } 35 | export function getLocationsNext(dongleId) { 36 | return request.get(`v1/navigation/${dongleId}/next`); 37 | } -------------------------------------------------------------------------------- /dist/billing.js: -------------------------------------------------------------------------------- 1 | import Config from './config'; 2 | import ConfigRequest from './instance'; 3 | const request = new ConfigRequest(Config.BILLING_URL_ROOT); 4 | export function configure(accessToken) { 5 | let errorResponseCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 6 | request.configure(accessToken, errorResponseCallback); 7 | } 8 | export async function getSubscription(dongle_id) { 9 | return request.get('v1/prime/subscription', { 10 | dongle_id 11 | }); 12 | } 13 | export async function getSubscribeInfo(dongle_id) { 14 | return request.get('v1/prime/subscribe_info', { 15 | dongle_id 16 | }); 17 | } 18 | export async function cancelPrime(dongle_id) { 19 | return request.post('v1/prime/cancel', { 20 | dongle_id 21 | }); 22 | } 23 | export async function getStripeCheckout(dongle_id, sim_id, plan) { 24 | return request.post('v1/prime/stripe_checkout', { 25 | dongle_id, 26 | sim_id, 27 | plan 28 | }); 29 | } 30 | export async function getStripePortal(dongle_id) { 31 | return request.get('v1/prime/stripe_portal', { 32 | dongle_id 33 | }); 34 | } 35 | export async function getStripeSession(dongle_id, session_id) { 36 | return request.get('v1/prime/stripe_session', { 37 | dongle_id, 38 | session_id 39 | }); 40 | } -------------------------------------------------------------------------------- /src/navigation.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export function setDestination(dongleId, latitude, longitude, place_name, place_details) { 5 | return request.post(`v1/navigation/${dongleId}/set_destination`, { 6 | latitude, 7 | longitude, 8 | place_name, 9 | place_details, 10 | }); 11 | } 12 | 13 | export function getLocationsData(dongleId) { 14 | return request.get(`v1/navigation/${dongleId}/locations`); 15 | } 16 | 17 | export function putLocationSave( 18 | dongleId, 19 | latitude, 20 | longitude, 21 | place_name, 22 | place_details, 23 | save_type, 24 | label, 25 | ) { 26 | return request.put(`v1/navigation/${dongleId}/locations`, { 27 | latitude, 28 | longitude, 29 | place_name, 30 | place_details, 31 | save_type, 32 | label, 33 | }); 34 | } 35 | 36 | export function patchLocationSave(dongleId, navLocationId, saveType, label) { 37 | return request.patch(`v1/navigation/${dongleId}/locations`, { 38 | id: navLocationId, 39 | save_type: saveType, 40 | label, 41 | }); 42 | } 43 | 44 | export function deleteLocationSave(dongleId, navLocationId) { 45 | return request.delete(`v1/navigation/${dongleId}/locations`, { id: navLocationId }); 46 | } 47 | 48 | export function getLocationsNext(dongleId) { 49 | return request.get(`v1/navigation/${dongleId}/next`); 50 | } 51 | -------------------------------------------------------------------------------- /src/raw.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | import request from './request'; 3 | 4 | 5 | // TODO: investigate whether to use IndexedDB or localStorage 6 | const urlStore = {}; 7 | 8 | async function getCached(endpoint, params, nocache) { 9 | let url = endpoint; 10 | if (params !== undefined) { 11 | url += `?${qs.stringify(params)}`; 12 | } 13 | 14 | // don't bother bouncing because the URLs themselves expire 15 | // our expiry time is from initial fetch time, not most recent access 16 | if (urlStore[url] && !nocache) { 17 | return urlStore[url]; 18 | } 19 | 20 | urlStore[url] = await request.get(url); 21 | 22 | setTimeout(() => { 23 | delete urlStore[url]; 24 | }, 1000 * 60 * 45); // expires in 1h, lets reset in 45m 25 | 26 | return urlStore[url]; 27 | } 28 | 29 | export function getRouteFiles(routeName, nocache = false, params = undefined) { 30 | return getCached(`v1/route/${routeName}/files`, params, nocache); 31 | } 32 | 33 | export function getLogUrls(routeName, params) { 34 | return getCached(`v1/route/${routeName}/log_urls`, params); 35 | } 36 | 37 | export function getUploadUrl(dongleId, path, expiry) { 38 | return getCached(`v1.4/${dongleId}/upload_url/`, { path, expiry_days: expiry }); 39 | } 40 | 41 | export async function getUploadUrls(dongleId, paths, expiry) { 42 | return request.post(`v1/${dongleId}/upload_urls/`, { paths, expiry_days: expiry }); 43 | } 44 | -------------------------------------------------------------------------------- /dist/raw.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | import request from './request'; 3 | 4 | // TODO: investigate whether to use IndexedDB or localStorage 5 | const urlStore = {}; 6 | async function getCached(endpoint, params, nocache) { 7 | let url = endpoint; 8 | if (params !== undefined) { 9 | url += `?${qs.stringify(params)}`; 10 | } 11 | 12 | // don't bother bouncing because the URLs themselves expire 13 | // our expiry time is from initial fetch time, not most recent access 14 | if (urlStore[url] && !nocache) { 15 | return urlStore[url]; 16 | } 17 | urlStore[url] = await request.get(url); 18 | setTimeout(() => { 19 | delete urlStore[url]; 20 | }, 1000 * 60 * 45); // expires in 1h, lets reset in 45m 21 | 22 | return urlStore[url]; 23 | } 24 | export function getRouteFiles(routeName) { 25 | let nocache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; 26 | let params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined; 27 | return getCached(`v1/route/${routeName}/files`, params, nocache); 28 | } 29 | export function getLogUrls(routeName, params) { 30 | return getCached(`v1/route/${routeName}/log_urls`, params); 31 | } 32 | export function getUploadUrl(dongleId, path, expiry) { 33 | return getCached(`v1.4/${dongleId}/upload_url/`, { 34 | path, 35 | expiry_days: expiry 36 | }); 37 | } 38 | export async function getUploadUrls(dongleId, paths, expiry) { 39 | return request.post(`v1/${dongleId}/upload_urls/`, { 40 | paths, 41 | expiry_days: expiry 42 | }); 43 | } -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-20.04 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: '16' 18 | cache: 'yarn' 19 | 20 | - run: yarn install --immutable --immutable-cache --check-cache 21 | - run: yarn lint 22 | - run: yarn build 23 | 24 | - name: Compare the expected and actual dist/ directories 25 | run: | 26 | if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then 27 | echo "The dist/ directory is out of date. Please run 'yarn build' and commit the changes." 28 | echo "See status below:" 29 | git diff 30 | exit 1 31 | fi 32 | 33 | build-api: 34 | runs-on: ubuntu-20.04 35 | steps: 36 | - uses: actions/checkout@v3 37 | - uses: actions/setup-node@v3 38 | with: 39 | node-version: '16' 40 | cache: 'yarn' 41 | 42 | - run: yarn install --immutable --immutable-cache --check-cache 43 | - run: yarn generate 44 | 45 | - name: Compare the expected and generated types 46 | run: | 47 | if [ "$(git diff --ignore-space-at-eol index.d.ts | wc -l)" -gt "0" ]; then 48 | echo "The types definitions are out of date. Please run 'yarn generate' and commit the changes." 49 | echo "See status below:" 50 | git diff 51 | exit 1 52 | fi 53 | -------------------------------------------------------------------------------- /src/drives.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export function getSegmentMetadata(start, end, dongleId) { 5 | return request.get(`v1/devices/${dongleId}/segments`, { 6 | from: start, 7 | to: end, 8 | }); 9 | } 10 | 11 | export function getRoutesSegments(dongleId, start, end, limit, route_str) { 12 | return request.get(`v1/devices/${dongleId}/routes_segments`, { 13 | start, end, limit, route_str, 14 | }); 15 | } 16 | 17 | export function getRouteInfo(routeName) { 18 | return request.get(`v1/route/${routeName}/`); 19 | } 20 | 21 | export function setRouteRating(routeName, rating) { 22 | return request.patch(`v1/route/${routeName}/`, { rating }); 23 | } 24 | 25 | export function setRoutePublic(routeName, is_public) { 26 | return request.patch(`v1/route/${routeName}/`, { is_public }); 27 | } 28 | 29 | export function setRoutePreserved(routeName, preserved) { 30 | return request.request(preserved ? 'POST' : 'DELETE', `v1/route/${routeName}/preserve`); 31 | } 32 | 33 | export function getPreservedRoutes(dongleId) { 34 | return request.get(`v1/devices/${dongleId}/routes/preserved`); 35 | } 36 | 37 | export function getShareSignature(routeName) { 38 | return request.get(`v1/route/${routeName}/share_signature`); 39 | } 40 | 41 | export function getRouteSegments(routeName) { 42 | return request.get(`v1/route/${routeName}/segments`); 43 | } 44 | 45 | export function listRoutes(dongleId, limit, createdAfter) { 46 | const params = { limit }; 47 | if (typeof createdAfter !== 'undefined') { 48 | params.createdAfter = createdAfter; 49 | } 50 | return request.get(`v1/devices/${dongleId}/routes`, params); 51 | } 52 | -------------------------------------------------------------------------------- /dist/devices.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | export function listDevices() { 3 | return request.get('v1/me/devices/'); 4 | } 5 | export function setDeviceAlias(dongleId, alias) { 6 | return request.patch(`v1/devices/${dongleId}/`, { 7 | alias 8 | }); 9 | } 10 | export function setDeviceVehicleId(dongleId, vehicle_id) { 11 | return request.patch(`v1/devices/${dongleId}/`, { 12 | vehicle_id 13 | }); 14 | } 15 | export function grantDeviceReadPermission(dongleId, email) { 16 | return request.post(`v1/devices/${dongleId}/add_user`, { 17 | email 18 | }); 19 | } 20 | export function removeDeviceReadPermission(dongleId, email) { 21 | return request.post(`v1/devices/${dongleId}/del_user`, { 22 | email 23 | }); 24 | } 25 | export async function fetchLocation(dongleId) { 26 | const locationEndpoint = `v1/devices/${dongleId}/location`; 27 | const location = await request.get(locationEndpoint); 28 | if (location !== undefined && location.error === undefined) { 29 | return location; 30 | } 31 | throw Error(`Could not fetch device location: ${JSON.stringify(location)}`); 32 | } 33 | export function fetchVehicles(vehicleId) { 34 | const vehicleEndpoint = `v1/vehicles/${vehicleId}`; 35 | return request.get(vehicleEndpoint); 36 | } 37 | export function fetchDevice(dongleId) { 38 | const deviceEndpoint = `v1.1/devices/${dongleId}/`; 39 | return request.get(deviceEndpoint); 40 | } 41 | export function pilotPair(pair_token) { 42 | return request.postForm('v2/pilotpair/', { 43 | pair_token 44 | }); 45 | } 46 | export function fetchDeviceStats(dongleId) { 47 | return request.get(`v1.1/devices/${dongleId}/stats`); 48 | } 49 | export function unpair(dongleId) { 50 | return request.post(`v1/devices/${dongleId}/unpair`); 51 | } 52 | export function fetchDeviceOwner(dongleId) { 53 | return request.get(`v1/devices/${dongleId}/owner`); 54 | } 55 | export function getAthenaQueue(dongleId) { 56 | return request.get(`v1/devices/${dongleId}/athena_offline_queue`); 57 | } -------------------------------------------------------------------------------- /src/devices.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | 3 | 4 | export function listDevices() { 5 | return request.get('v1/me/devices/'); 6 | } 7 | 8 | export function setDeviceAlias(dongleId, alias) { 9 | return request.patch(`v1/devices/${dongleId}/`, { alias }); 10 | } 11 | 12 | export function setDeviceVehicleId(dongleId, vehicle_id) { 13 | return request.patch(`v1/devices/${dongleId}/`, { vehicle_id }); 14 | } 15 | 16 | export function grantDeviceReadPermission(dongleId, email) { 17 | return request.post(`v1/devices/${dongleId}/add_user`, { email }); 18 | } 19 | 20 | export function removeDeviceReadPermission(dongleId, email) { 21 | return request.post(`v1/devices/${dongleId}/del_user`, { email }); 22 | } 23 | 24 | export async function fetchLocation(dongleId) { 25 | const locationEndpoint = `v1/devices/${dongleId}/location`; 26 | const location = await request.get(locationEndpoint); 27 | if (location !== undefined && location.error === undefined) { 28 | return location; 29 | } 30 | throw Error(`Could not fetch device location: ${JSON.stringify(location)}`); 31 | } 32 | 33 | export function fetchVehicles(vehicleId) { 34 | const vehicleEndpoint = `v1/vehicles/${vehicleId}`; 35 | return request.get(vehicleEndpoint); 36 | } 37 | 38 | export function fetchDevice(dongleId) { 39 | const deviceEndpoint = `v1.1/devices/${dongleId}/`; 40 | return request.get(deviceEndpoint); 41 | } 42 | 43 | export function pilotPair(pair_token) { 44 | return request.postForm('v2/pilotpair/', { pair_token }); 45 | } 46 | 47 | export function fetchDeviceStats(dongleId) { 48 | return request.get(`v1.1/devices/${dongleId}/stats`); 49 | } 50 | 51 | export function unpair(dongleId) { 52 | return request.post(`v1/devices/${dongleId}/unpair`); 53 | } 54 | 55 | export function fetchDeviceOwner(dongleId) { 56 | return request.get(`v1/devices/${dongleId}/owner`); 57 | } 58 | 59 | export function getAthenaQueue(dongleId) { 60 | return request.get(`v1/devices/${dongleId}/athena_offline_queue`); 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: build api docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | docker: 16 | runs-on: ubuntu-20.04 17 | if: github.repository == 'commaai/comma-api' 18 | permissions: 19 | packages: write 20 | contents: read 21 | steps: 22 | - uses: actions/checkout@v3 23 | - id: buildx 24 | uses: docker/setup-buildx-action@v2 25 | 26 | - name: Cache Docker layers 27 | uses: actions/cache@v2 28 | with: 29 | path: /tmp/.buildx-cache 30 | key: ${{ runner.os }}-buildx-${{ github.sha }} 31 | restore-keys: | 32 | ${{ runner.os }}-buildx- 33 | 34 | - name: login to container registry 35 | uses: docker/login-action@v2 36 | with: 37 | registry: ghcr.io 38 | username: ${{ github.actor }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - id: meta 42 | uses: docker/metadata-action@v4 43 | with: 44 | images: ghcr.io/commaai/api-docs 45 | tags: | 46 | type=raw,value=latest,enable={{is_default_branch}} 47 | type=ref,event=branch 48 | type=raw,${{ github.sha }} 49 | type=raw,enable=${{ github.event_name == 'pull_request' }},value=${{ github.head_ref }} 50 | 51 | - name: Build and push 52 | uses: docker/build-push-action@v3 53 | with: 54 | build-args: SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }} 55 | builder: ${{ steps.buildx.outputs.name }} 56 | context: . 57 | push: true 58 | tags: ${{ steps.meta.outputs.tags }} 59 | labels: ${{ steps.meta.outputs.labels }} 60 | cache-from: type=local,src=/tmp/.buildx-cache 61 | cache-to: type=local,dest=/tmp/.buildx-cache-new 62 | 63 | - name: Move cache 64 | run: | 65 | rm -rf /tmp/.buildx-cache 66 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 67 | -------------------------------------------------------------------------------- /src/instance.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | 3 | 4 | export class RequestError extends Error { 5 | constructor(resp, ...params) { 6 | super(...params); 7 | this.resp = resp; 8 | } 9 | } 10 | 11 | export default class ConfigRequest { 12 | constructor(baseUrl) { 13 | this.defaultHeaders = { 14 | 'Content-Type': 'application/json', 15 | }; 16 | this.baseUrl = baseUrl + (!baseUrl.endsWith('/') ? '/' : ''); 17 | this.errorResponseCallback = null; 18 | } 19 | 20 | configure(accessToken, errorResponseCallback = null) { 21 | if (accessToken) { 22 | this.defaultHeaders.Authorization = `JWT ${accessToken}`; 23 | } 24 | if (errorResponseCallback) { 25 | this.errorResponseCallback = errorResponseCallback; 26 | } 27 | } 28 | 29 | async request(method, endpoint, params, dataJson = true, respJson = true) { 30 | const headers = { ...this.defaultHeaders }; 31 | if (!dataJson) { 32 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 33 | } 34 | 35 | let requestUrl = this.baseUrl + endpoint; 36 | let body; 37 | if (params && Object.keys(params).length !== 0) { 38 | if (method === 'GET' || method === 'HEAD') { 39 | requestUrl += `?${qs.stringify(params)}`; 40 | } else if (dataJson) { 41 | body = JSON.stringify(params); 42 | } else { 43 | body = qs.stringify(params); 44 | } 45 | } 46 | 47 | const resp = await fetch(requestUrl, { method, headers, body }); 48 | if (!resp.ok) { 49 | if (this.errorResponseCallback) { 50 | await this.errorResponseCallback(resp); 51 | return null; 52 | } 53 | const error = await resp.text(); 54 | throw new RequestError(resp, `${resp.status}: ${error}`); 55 | } 56 | if (!respJson) { 57 | return resp; 58 | } 59 | return resp.json(); 60 | } 61 | 62 | async get(endpoint, params, dataJson = true, respJson = true) { 63 | return this.request('GET', endpoint, params, dataJson, respJson); 64 | } 65 | 66 | async head(endpoint, params, dataJson = true, respJson = true) { 67 | return this.request('HEAD', endpoint, params, dataJson, respJson); 68 | } 69 | 70 | async post(endpoint, params, dataJson = true, respJson = true) { 71 | return this.request('POST', endpoint, params, dataJson, respJson); 72 | } 73 | 74 | async postForm(endpoint, params) { 75 | return this.post(endpoint, params, false); 76 | } 77 | 78 | async put(endpoint, params, dataJson = true, respJson = true) { 79 | return this.request('PUT', endpoint, params, dataJson, respJson); 80 | } 81 | 82 | async delete(endpoint, params, dataJson = true, respJson = true) { 83 | return this.request('DELETE', endpoint, params, dataJson, respJson); 84 | } 85 | 86 | async patch(endpoint, params, dataJson = true, respJson = true) { 87 | return this.request('PATCH', endpoint, params, dataJson, respJson); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /dist/instance.js: -------------------------------------------------------------------------------- 1 | import qs from 'query-string'; 2 | export class RequestError extends Error { 3 | constructor(resp) { 4 | for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 5 | params[_key - 1] = arguments[_key]; 6 | } 7 | super(...params); 8 | this.resp = resp; 9 | } 10 | } 11 | export default class ConfigRequest { 12 | constructor(baseUrl) { 13 | this.defaultHeaders = { 14 | 'Content-Type': 'application/json' 15 | }; 16 | this.baseUrl = baseUrl + (!baseUrl.endsWith('/') ? '/' : ''); 17 | this.errorResponseCallback = null; 18 | } 19 | configure(accessToken) { 20 | let errorResponseCallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 21 | if (accessToken) { 22 | this.defaultHeaders.Authorization = `JWT ${accessToken}`; 23 | } 24 | if (errorResponseCallback) { 25 | this.errorResponseCallback = errorResponseCallback; 26 | } 27 | } 28 | async request(method, endpoint, params) { 29 | let dataJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 30 | let respJson = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; 31 | const headers = { 32 | ...this.defaultHeaders 33 | }; 34 | if (!dataJson) { 35 | headers['Content-Type'] = 'application/x-www-form-urlencoded'; 36 | } 37 | let requestUrl = this.baseUrl + endpoint; 38 | let body; 39 | if (params && Object.keys(params).length !== 0) { 40 | if (method === 'GET' || method === 'HEAD') { 41 | requestUrl += `?${qs.stringify(params)}`; 42 | } else if (dataJson) { 43 | body = JSON.stringify(params); 44 | } else { 45 | body = qs.stringify(params); 46 | } 47 | } 48 | const resp = await fetch(requestUrl, { 49 | method, 50 | headers, 51 | body 52 | }); 53 | if (!resp.ok) { 54 | if (this.errorResponseCallback) { 55 | await this.errorResponseCallback(resp); 56 | return null; 57 | } 58 | const error = await resp.text(); 59 | throw new RequestError(resp, `${resp.status}: ${error}`); 60 | } 61 | if (!respJson) { 62 | return resp; 63 | } 64 | return resp.json(); 65 | } 66 | async get(endpoint, params) { 67 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 68 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 69 | return this.request('GET', endpoint, params, dataJson, respJson); 70 | } 71 | async head(endpoint, params) { 72 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 73 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 74 | return this.request('HEAD', endpoint, params, dataJson, respJson); 75 | } 76 | async post(endpoint, params) { 77 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 78 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 79 | return this.request('POST', endpoint, params, dataJson, respJson); 80 | } 81 | async postForm(endpoint, params) { 82 | return this.post(endpoint, params, false); 83 | } 84 | async put(endpoint, params) { 85 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 86 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 87 | return this.request('PUT', endpoint, params, dataJson, respJson); 88 | } 89 | async delete(endpoint, params) { 90 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 91 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 92 | return this.request('DELETE', endpoint, params, dataJson, respJson); 93 | } 94 | async patch(endpoint, params) { 95 | let dataJson = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; 96 | let respJson = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true; 97 | return this.request('PATCH', endpoint, params, dataJson, respJson); 98 | } 99 | } -------------------------------------------------------------------------------- /dist/drives.js: -------------------------------------------------------------------------------- 1 | import request from './request'; 2 | const SEGMENT_LENGTH = 1000 * 60; 3 | export function getSegmentMetadata(start, end, dongleId) { 4 | return request.get(`v1/devices/${dongleId}/segments`, { 5 | from: start, 6 | to: end 7 | }); 8 | } 9 | export function getRoutesSegments(dongleId, start, end, limit, route_str) { 10 | return request.get(`v1/devices/${dongleId}/routes_segments`, { 11 | start, 12 | end, 13 | limit, 14 | route_str 15 | }); 16 | } 17 | export function getRouteInfo(routeName) { 18 | return request.get(`v1/route/${routeName}/`); 19 | } 20 | export function setRouteRating(routeName, rating) { 21 | return request.patch(`v1/route/${routeName}/`, { 22 | rating 23 | }); 24 | } 25 | export function setRoutePublic(routeName, is_public) { 26 | return request.patch(`v1/route/${routeName}/`, { 27 | is_public 28 | }); 29 | } 30 | export function setRoutePreserved(routeName, preserved) { 31 | return request.request(preserved ? 'POST' : 'DELETE', `v1/route/${routeName}/preserve`); 32 | } 33 | export function getPreservedRoutes(dongleId) { 34 | return request.get(`v1/devices/${dongleId}/routes/preserved`); 35 | } 36 | export function getShareSignature(routeName) { 37 | return request.get(`v1/route/${routeName}/share_signature`); 38 | } 39 | export function getRouteSegments(routeName) { 40 | return request.get(`v1/route/${routeName}/segments`); 41 | } 42 | export function listRoutes(dongleId, limit, createdAfter) { 43 | const params = { 44 | limit 45 | }; 46 | if (typeof createdAfter !== 'undefined') { 47 | params.createdAfter = createdAfter; 48 | } 49 | return request.get(`v1/devices/${dongleId}/routes`, params); 50 | } 51 | function parseSegmentMetadata(start, end, segments) { 52 | const routeStartTimes = {}; 53 | return segments.map(s => { 54 | const segment = { 55 | ...s, 56 | duration: Math.round(s.end_time_utc_millis - s.start_time_utc_millis), 57 | offset: Math.round(s.start_time_utc_millis) - start 58 | }; 59 | if (!routeStartTimes[s.canonical_route_name]) { 60 | segment.segment = Number(s.canonical_name.split('--')[2]); 61 | routeStartTimes[s.canonical_route_name] = segment.offset - SEGMENT_LENGTH * segment.segment; 62 | } 63 | segment.routeOffset = routeStartTimes[s.canonical_route_name]; 64 | return segment; 65 | }); 66 | } 67 | 68 | // TODO: understand this and write tests 69 | function segmentsFromMetadata(segmentsData) { 70 | function finishSegment(segment) { 71 | if (!segment.hasVideo) { 72 | return; 73 | } 74 | const { 75 | videoAvailableBetweenOffsets 76 | } = segment; 77 | let lastVideoRange = videoAvailableBetweenOffsets[videoAvailableBetweenOffsets.length - 1]; 78 | if (!lastVideoRange) { 79 | lastVideoRange = [segment.offset, segment.offset + segment.duration]; 80 | } 81 | 82 | // TODO: refactor 83 | // eslint-disable-next-line no-param-reassign 84 | segment.videoAvailableBetweenOffsets = [...videoAvailableBetweenOffsets.slice(0, videoAvailableBetweenOffsets.length - 1), [lastVideoRange[0], segment.offset + segment.duration]]; 85 | } 86 | let segment = null; 87 | let videoStartOffset = null; 88 | const segments = []; 89 | segmentsData.forEach(s => { 90 | if (!s.url) { 91 | return; 92 | } 93 | if (!(s.proc_log === 40 || s.proc_qlog === 40)) { 94 | return; 95 | } 96 | const segmentHasDriverCamera = s.proc_dcamera >= 0; 97 | const segmentHasDriverCameraStream = s.proc_dcamera === 40; 98 | const segmentHasVideo = s.proc_camera === 40; 99 | if (segmentHasVideo && videoStartOffset === null) { 100 | videoStartOffset = s.offset; 101 | } 102 | if (!segment || segment.route !== s.canonical_route_name) { 103 | if (segment) { 104 | finishSegment(segment); 105 | } 106 | let { 107 | url 108 | } = s; 109 | const parts = url.split('/'); 110 | if (Number.isFinite(Number(parts.pop()))) { 111 | // url has a number at the end 112 | url = parts.join('/'); 113 | } 114 | segment = { 115 | dongleId: s.dongle_id, 116 | offset: s.offset - s.segment * SEGMENT_LENGTH, 117 | route: s.canonical_route_name, 118 | startTime: s.start_time_utc_millis, 119 | startCoord: [s.start_lng, s.start_lat], 120 | duration: 0, 121 | segments: 0, 122 | url: url.replace('chffrprivate.blob.core.windows.net', 'chffrprivate.azureedge.net'), 123 | events: [], 124 | videoAvailableBetweenOffsets: [], 125 | hasVideo: segmentHasVideo, 126 | deviceType: s.devicetype, 127 | hpgps: s.hpgps, 128 | hasDriverCamera: segmentHasDriverCamera, 129 | hasDriverCameraStream: segmentHasDriverCameraStream, 130 | locStart: '', 131 | locEnd: '', 132 | distanceMiles: 0.0, 133 | cameraStreamSegCount: 0, 134 | driverCameraStreamSegCount: 0 135 | }; 136 | segments.push(segment); 137 | } 138 | if (!segmentHasVideo && videoStartOffset !== null) { 139 | segment.videoAvailableBetweenOffsets.push([videoStartOffset, s.offset]); 140 | videoStartOffset = null; 141 | } 142 | segment.hasVideo = segment.hasVideo || segmentHasVideo; 143 | segment.hasDriverCamera = segment.hasDriverCamera || segmentHasDriverCamera; 144 | segment.hasDriverCameraStream = segment.hasDriverCameraStream || segmentHasDriverCameraStream; 145 | segment.hpgps = segment.hpgps || s.hpgps; 146 | segment.duration = s.offset - segment.offset + s.duration; 147 | segment.segments = Math.max(segment.segments, Number(s.canonical_name.split('--').pop()) + 1); 148 | segment.events = segment.events.concat(s.events); 149 | segment.endCoord = [s.end_lng, s.end_lat]; 150 | segment.distanceMiles += s.length; 151 | segment.cameraStreamSegCount += Math.floor(segmentHasVideo); 152 | segment.driverCameraStreamSegCount += Math.floor(segmentHasDriverCameraStream); 153 | }); 154 | if (segment) { 155 | finishSegment(segment); 156 | } 157 | return segments; 158 | } 159 | export async function fetchRoutes(dongleId, start, end) { 160 | let segments = await getSegmentMetadata(start, end, dongleId); 161 | segments = parseSegmentMetadata(start, end, segments); 162 | return segmentsFromMetadata(segments).reverse(); 163 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was auto-generated by openapi-typescript. 3 | * Do not make direct changes to the file. 4 | */ 5 | 6 | 7 | /** Type helpers */ 8 | type Without = { [P in Exclude]?: never }; 9 | type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; 10 | type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; 11 | 12 | export interface paths { 13 | "/v1/me": { 14 | /** 15 | * User profile 16 | * @description Returns information about the authenticated user 17 | */ 18 | get: operations["getProfile"]; 19 | }; 20 | "/v1/me/devices": { 21 | /** 22 | * Device list 23 | * @description List devices owned or readable by authenticated user 24 | */ 25 | get: operations["getDevices"]; 26 | }; 27 | "/v1/{dongleId}/devices": { 28 | /** 29 | * List devices (superuser) 30 | * @description List devices owned or readable by specified user 31 | */ 32 | get: operations["getUserDevices"]; 33 | 34 | }; 35 | "/v1.1/devices/{dongleId}": { 36 | /** 37 | * Device details 38 | * @description Returns information about the specified device 39 | */ 40 | get: operations["getDevice"]; 41 | 42 | }; 43 | "/v1/devices/{dongleId}": { 44 | /** Update device alias */ 45 | patch: operations["updateDevice"]; 46 | 47 | }; 48 | "/v1/devices/{dongleId}/location": { 49 | /** Device location */ 50 | get: operations["getDeviceLocation"]; 51 | 52 | }; 53 | "/v1/devices/{dongleId}/pair": { 54 | /** 55 | * Pair device 56 | * @description Pair a device to a user's account. 57 | */ 58 | post: operations["pairDeviceToUser"]; 59 | 60 | }; 61 | "/v1/devices/{dongleId}/unpair": { 62 | /** 63 | * Unpair device 64 | * @description Unpair device from authenticated user's account. Any comma prime subscription linked to the device will be cancelled. 65 | */ 66 | post: operations["unpairDevice"]; 67 | 68 | }; 69 | "/v1/devices/{dongleId}/owner": { 70 | /** 71 | * Device owner 72 | * @description Returns the owner of a device. 73 | */ 74 | get: operations["getDeviceOwner"]; 75 | 76 | }; 77 | "/v1/devices/{dongleId}/users": { 78 | /** 79 | * Device users 80 | * @description List users with access to a device 81 | */ 82 | get: operations["getDeviceUsers"]; 83 | 84 | }; 85 | "/v1/devices/{dongleId}/add_user": { 86 | /** 87 | * Grant device access 88 | * @description Grant read permissions to a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is granted to all users. 89 | */ 90 | post: operations["addDeviceUser"]; 91 | 92 | }; 93 | "/v1/devices/{dongleId}/del_user": { 94 | /** 95 | * Revoke device access 96 | * @description Revoke read permissions from a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is removed from all users. 97 | */ 98 | post: operations["revokeDeviceUser"]; 99 | 100 | }; 101 | "/v1.1/devices/{dongleId}/stats": { 102 | /** 103 | * Device driving statistics 104 | * @description Returns aggregate driving statistics for a device over the past 7 days and all time. 105 | */ 106 | get: operations["getDeviceStatistics"]; 107 | 108 | }; 109 | "/v1/devices/{dongleId}/bootlogs": { 110 | /** 111 | * Device boot logs 112 | * @description Retrieve boot logs uploaded from a device. 113 | */ 114 | get: operations["getDeviceBootLogs"]; 115 | 116 | }; 117 | "/v1/devices/{dongleId}/crashlogs": { 118 | /** 119 | * Device crash logs 120 | * @description Retrieve crash logs uploaded from a device. 121 | */ 122 | get: operations["getDeviceCrashLogs"]; 123 | 124 | }; 125 | "/v1/devices/{dongleId}/routes": { 126 | /** 127 | * Device routes 128 | * @description Returns a list of routes uploaded from a device. 129 | */ 130 | get: operations["getDeviceRoutes"]; 131 | 132 | }; 133 | "/v1/devices/{dongleId}/routes/preserved": { 134 | /** 135 | * Device preserved routes 136 | * @description Returns a list of preserved routes uploaded from a device. 137 | */ 138 | get: operations["getDevicePreservedRoutes"]; 139 | 140 | }; 141 | "/v1/devices/{dongleId}/routes_segments": { 142 | /** 143 | * Device routes segments 144 | * @description Returns a list of route segments uploaded from a device between a start and end timestamp. 145 | */ 146 | get: operations["getDeviceRoutesSegments"]; 147 | 148 | }; 149 | "/v1/devices/{dongleId}/segments": { 150 | /** 151 | * Device segments 152 | * @description Returns time-sorted list of segments, each of which includes basic metadata derived from openpilot logs. 153 | */ 154 | get: operations["getDeviceSegments"]; 155 | 156 | }; 157 | "/{dongleId}": { 158 | /** 159 | * Submit Athena payload 160 | * @description Send JSON-RPC requests to the active websocket connection for a given device, identified by its dongle ID. 161 | * 162 | * Some method types support offline queueing of messages until the device comes online (see `expiry`). The 163 | * response will indicate whether the message was queued or not. 164 | */ 165 | post: operations["submitAthenaPayload"]; 166 | 167 | }; 168 | "/v1/devices/{dongleId}/athena_offline_queue": { 169 | /** 170 | * Athena offline queue 171 | * @description Return a list of queued payloads for delivery to device when it is online. 172 | */ 173 | get: operations["getDeviceAthenaOfflineQueue"]; 174 | 175 | }; 176 | "/v1.4/{dongleId}/upload_url": { 177 | /** 178 | * Log file upload 179 | * @description Request a URL to which an openpilot file an be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 180 | */ 181 | get: operations["getUploadUrl"]; 182 | 183 | }; 184 | "/v1/{dongleId}/upload_urls": { 185 | /** 186 | * Batch log file upload 187 | * @description Request URLs to which openpilot files can be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 188 | */ 189 | post: operations["getUploadUrls"]; 190 | 191 | }; 192 | "/v2/pilotpair": { 193 | /** 194 | * Pair device 195 | * @description Pair a device to the authenticated user's account. 196 | */ 197 | post: operations["pilotPair"]; 198 | }; 199 | "/v2/pilotauth": { 200 | /** Authenticate device (openpilot) */ 201 | post: operations["pilotAuth"]; 202 | }; 203 | "/v1/route/{routeName}": { 204 | /** 205 | * Route details 206 | * @description Returns information about the specified route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 207 | */ 208 | get: operations["getRoute"]; 209 | /** 210 | * Update route 211 | * @description Update route metadata. Authenticated user must have ownership of the device from which the route was uploaded. 212 | */ 213 | patch: operations["updateRoute"]; 214 | 215 | }; 216 | "/v1/route/{routeName}/segments": { 217 | /** 218 | * Route segments 219 | * @description Returns list of segments comprising a route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 220 | */ 221 | get: operations["getRouteSegments"]; 222 | 223 | }; 224 | "/v1/route/{routeName}/files": { 225 | /** 226 | * Raw log files 227 | * @description Retrieve uploaded files for a route. Calls to this API are rate limited to 5 per minute. 228 | */ 229 | get: operations["getRouteFiles"]; 230 | 231 | }; 232 | "/v1/route/{routeName}/qcamera.m3u8": { 233 | /** 234 | * Route HLS stream 235 | * @description Returns rear camera HLS stream index of MPEG-TS fragments. 236 | */ 237 | get: operations["getRouteStream"]; 238 | 239 | }; 240 | "/v1/route/{routeName}/share_signature": { 241 | /** 242 | * Route sharing signature 243 | * @description Return route share URL signature. Expires in 365 days. 244 | */ 245 | get: operations["getRouteShareSignature"]; 246 | 247 | }; 248 | "/v1/route/{routeName}/preserve": { 249 | /** 250 | * Preserve route 251 | * @description Preserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 252 | */ 253 | post: operations["preserveRoute"]; 254 | /** 255 | * Unpreserve route 256 | * @description Unpreserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 257 | */ 258 | delete: operations["unpreserveRoute"]; 259 | 260 | }; 261 | "/v1/tokens/mapbox/{dongleId}": { 262 | /** 263 | * Mapbox token 264 | * @description Returns a Mapbox token for the specified dongle ID. Authenticated user must have ownership of the dongle ID. 265 | */ 266 | get: operations["getMapboxToken"]; 267 | 268 | }; 269 | "/v1/navigation/{dongleId}/set_destination": { 270 | /** 271 | * Set nav destination 272 | * @description Set destination for navigation. Authenticated user must have ownership of the dongle ID. 273 | */ 274 | post: operations["setDestination"]; 275 | 276 | }; 277 | "/v1/navigation/{dongleId}/next": { 278 | /** 279 | * Get nav destination 280 | * @description Retrieve next location from database. This was set on Set destination if the device was offline. Next location is removed from the database after this call or when a new destination is set. 281 | */ 282 | get: operations["getNavigationNext"]; 283 | /** 284 | * Clear nav destination 285 | * @description Delete next destination from database. 286 | */ 287 | delete: operations["clearNavigationNext"]; 288 | 289 | }; 290 | "/v1/navigation/{dongleId}/locations": { 291 | /** 292 | * Saved locations 293 | * @description Retrieve saved locations from database. 294 | */ 295 | get: operations["getNavigationSavedLocations"]; 296 | /** Save location */ 297 | put: operations["saveNavigationLocation"]; 298 | /** Delete location */ 299 | delete: operations["deleteNavigationLocation"]; 300 | /** Update location */ 301 | patch: operations["updateNavigationLocation"]; 302 | 303 | }; 304 | "/v1/clips/create": { 305 | /** 306 | * Create clip 307 | * @description Create a clip from a route. 308 | */ 309 | post: operations["createClip"]; 310 | }; 311 | "/v1/clips/list": { 312 | /** 313 | * List clips 314 | * @description List clips created for the specified device. 315 | */ 316 | get: operations["getClips"]; 317 | }; 318 | "/v1/clips/details": { 319 | /** Get clip details */ 320 | get: operations["getClip"]; 321 | }; 322 | "/v1/clips/update": { 323 | /** Delete clip */ 324 | delete: operations["deleteClip"]; 325 | /** Update clip */ 326 | patch: operations["updateClip"]; 327 | }; 328 | } 329 | 330 | export type webhooks = Record; 331 | 332 | export interface components { 333 | schemas: { 334 | Profile: { 335 | /** 336 | * Format: email 337 | * @description Email address 338 | * @example commaphone3@gmail.com 339 | */ 340 | email: string; 341 | /** 342 | * @description Dongle ID 343 | * @example 2e9eeac96ea4e6a6 344 | */ 345 | id: string; 346 | /** 347 | * @deprecated 348 | * @description comma points 349 | * @example 34933 350 | */ 351 | points: number; 352 | /** 353 | * @description Unix timestamp at time of registration 354 | * @example 1465103707 355 | */ 356 | regdate: number; 357 | /** 358 | * @description Apply for superuser here 359 | * @example false 360 | */ 361 | superuser: boolean; 362 | /** 363 | * @deprecated 364 | * @description Username 365 | * @example joeyjoejoe 366 | */ 367 | username?: string | null; 368 | /** 369 | * @description OAuth2 user ID 370 | * @example google_111803823964622526972 371 | */ 372 | user_id: string; 373 | }; 374 | Device: { 375 | dongle_id: components["schemas"]["DongleID"]; 376 | /** @description Device nickname */ 377 | alias: string; 378 | /** @description Device serial number */ 379 | serial: string; 380 | /** @description Last connected athena server hostname */ 381 | athena_host?: string | null; 382 | /** @description Unix timestamp of last athena ping */ 383 | last_athena_ping?: number; 384 | /** @description Uploads are ignored from this device */ 385 | ignore_uploads?: boolean | null; 386 | /** @description Device has an owner */ 387 | is_paired: boolean; 388 | /** @description Authenticated user has write-access to the device */ 389 | is_owner?: boolean; 390 | /** @description 2048-bit public RSA key */ 391 | public_key: string | null; 392 | /** @description Device has a prime subscription */ 393 | prime: boolean; 394 | /** 395 | * @description Prime subscription type 396 | * - 0 = None 397 | * - 1 = Magenta 398 | * - 2 = Lite 399 | * - 3 = Blue 400 | * - 4 = Magenta New 401 | * 402 | * @enum {number} 403 | */ 404 | prime_type: 0 | 1 | 2 | 3 | 4; 405 | /** @description Device prime trial is claimed */ 406 | trial_claimed: boolean | null; 407 | /** 408 | * @description Device type 409 | * @enum {string} 410 | */ 411 | device_type: "app" | "neo" | "panda" | "two" | "freon" | "pc" | "three"; 412 | /** @description Unix timestamp, in milliseconds */ 413 | last_gps_time: number | null; 414 | /** @description Latitude, in decimal degrees */ 415 | last_gps_lat: number | null; 416 | /** @description Longitude, in decimal degrees */ 417 | last_gps_lng: number | null; 418 | /** @description Accuracy, in metres */ 419 | last_gps_accuracy: number | null; 420 | /** @description Speed, in metres per second */ 421 | last_gps_speed: number | null; 422 | /** @description Direction angle, in degrees from north */ 423 | last_gps_bearing: number | null; 424 | /** @description openpilot version */ 425 | openpilot_version?: string | null; 426 | /** @description Last known SIM ID of the device */ 427 | sim_id: string | null; 428 | }; 429 | DeviceUser: { 430 | /** @description User email */ 431 | email: string; 432 | permission: components["schemas"]["DeviceUserPermission"]; 433 | }; 434 | /** 435 | * @description Device user permission 436 | * @enum {string} 437 | */ 438 | DeviceUserPermission: "read_access" | "owner"; 439 | DeviceLocation: { 440 | /** @description Latitude, in decimal degrees */ 441 | lat: number; 442 | /** @description Longitude, in decimal degrees */ 443 | lng: number; 444 | /** @description Unix timestamp, in milliseconds */ 445 | time: number; 446 | /** @description Accuracy, in metres */ 447 | accuracy: number; 448 | /** @description Speed, in metres per second */ 449 | speed: number; 450 | /** @description Direction angle, in degrees from north */ 451 | bearing: number; 452 | }; 453 | /** @description Summary of drives over a period of time */ 454 | DrivingStatistics: { 455 | /** @description Sum of distance driven in time period, in miles */ 456 | distance: number; 457 | /** @description Sum of time driven in time period, in minutes */ 458 | minutes: number; 459 | /** @description Count of routes in time period */ 460 | routes: number; 461 | }; 462 | /** @description A single segment of a route is up to 60 seconds in length. */ 463 | Segment: { 464 | canonical_name: components["schemas"]["SegmentName"]; 465 | /** @description Segment number */ 466 | number: number; 467 | canonical_route_name: components["schemas"]["RouteName"]; 468 | dongle_id: components["schemas"]["DongleID"]; 469 | /** @description Unix timestamp at which upload_url was first called for file in segment */ 470 | create_time: number; 471 | /** @description Segment start time, milliseconds since epoch */ 472 | start_time_utc_millis: number; 473 | /** @description Segment end time, milliseconds since epoch */ 474 | end_time_utc_millis: number; 475 | /** @description Signed URL from which route.coords and JPEGs can be downloaded */ 476 | url: string; 477 | /** @description Sum of distances between GPS points in miles */ 478 | length: number; 479 | /** @description Segment contains CAN messages */ 480 | can: boolean; 481 | /** @description Segment has ublox packets */ 482 | hpgps: boolean; 483 | /** @description Segment contains radar tracks in CAN */ 484 | radar: boolean; 485 | devicetype: components["schemas"]["SegmentDataSource"]; 486 | proc_log: components["schemas"]["FileProcStatus"]; 487 | proc_qlog: components["schemas"]["FileProcStatus"]; 488 | proc_camera: components["schemas"]["FileProcStatus"]; 489 | proc_dcamera: components["schemas"]["FileProcStatus"]; 490 | /** @description openpilot is running in passive mode */ 491 | passive: boolean; 492 | /** @description openpilot version */ 493 | version: string; 494 | /** @description git commit hash */ 495 | git_commit: string; 496 | /** @description git branch */ 497 | git_branch: string; 498 | /** @description git remote url */ 499 | git_remote: string; 500 | /** @description git working tree is dirty */ 501 | git_dirty: boolean; 502 | }; 503 | Route: { 504 | fullname: components["schemas"]["RouteName"]; 505 | dongle_id: components["schemas"]["DongleID"]; 506 | user_id: components["schemas"]["DongleID"]; 507 | /** @description Route is publicly accessible */ 508 | is_public?: boolean; 509 | /** @description Unix timestamp at which upload_url was first called for file in route */ 510 | create_time: number; 511 | /** @description Signed storage bucket URL from which route.coords and JPEGs can be downloaded */ 512 | url: string; 513 | /** @description Unix timestamp at which signed URL expires */ 514 | share_expiry: number; 515 | /** @description URL signature */ 516 | share_sig: string; 517 | /** @description Sum of distances between GPS points in miles */ 518 | length: number; 519 | /** @description Route contains CAN messages */ 520 | can?: boolean; 521 | /** @description Route has ublox packets */ 522 | hpgps?: boolean; 523 | /** @description Route contains radar tracks in CAN */ 524 | radar?: boolean; 525 | devicetype: components["schemas"]["SegmentDataSource"]; 526 | /** @description Maximum qlog segment number uploaded */ 527 | maxqlog?: number; 528 | /** @description Maximum qcamera segment number uploaded */ 529 | maxqcamera?: number; 530 | /** @description Maximum log segment number uploaded */ 531 | maxlog: number; 532 | /** @description Maximum road camera segment number uploaded */ 533 | maxcamera: number; 534 | /** @description Maximum driver camera segment number uploaded */ 535 | maxdcamera: number; 536 | /** @description Maximum wide road camera segment number uploaded */ 537 | maxecamera?: number; 538 | /** @description Maximum qlog segment number processed */ 539 | procqlog?: number; 540 | /** @description Maximum qcamera segment number processed */ 541 | procqcamera?: number; 542 | /** @description Maximum log segment number processed */ 543 | proclog: number; 544 | /** @description Maximum road camera segment number processed */ 545 | proccamera: number; 546 | /** @description First latitude recorded in route from GPS */ 547 | start_lat?: number; 548 | /** @description First longitude recorded in route from GPS */ 549 | start_lng?: number; 550 | /** @description Unix timestamp at beginning of route */ 551 | start_time: number; 552 | /** @description Last latitude recorded in route from GPS */ 553 | end_lat?: number; 554 | /** @description Last longitude recorded in route from GPS */ 555 | end_lng?: number; 556 | /** @description Unix timestamp at end of last segment in route */ 557 | end_time: number; 558 | /** @description openpilot is running in passive mode */ 559 | passive?: boolean; 560 | /** @description openpilot version */ 561 | version?: string; 562 | /** @description git commit hash */ 563 | git_commit?: string; 564 | /** @description git branch */ 565 | git_branch?: string; 566 | /** @description git remote url */ 567 | git_remote?: string; 568 | /** @description git working tree is dirty */ 569 | git_dirty?: boolean; 570 | /** @description openpilot platform name */ 571 | platform?: string; 572 | vin?: components["schemas"]["VIN"]; 573 | /** @description Minimum logMonoTime from openpilot log */ 574 | init_logmonotime?: number; 575 | }; 576 | RouteSegment: components["schemas"]["Route"] & { 577 | /** @description Segment numbers in route */ 578 | segment_numbers: (number)[]; 579 | /** @description Segment start times in milliseconds since epoch */ 580 | segment_start_times: (number)[]; 581 | /** @description Segment end times in milliseconds since epoch */ 582 | segment_end_times: (number)[]; 583 | }; 584 | /** 585 | * @description Device type 586 | * @enum {string} 587 | */ 588 | DeviceType: "app" | "neo" | "panda" | "two" | "freon" | "pc" | "three"; 589 | /** 590 | * @description Data source 591 | * - 3 = eon 592 | * - 6 = comma two 593 | * - 7 = comma three 594 | * 595 | * @enum {integer} 596 | */ 597 | SegmentDataSource: 3 | 6 | 7; 598 | /** 599 | * @description Log file status 600 | * - -1 = Not received 601 | * - 0 = Upload URL sent 602 | * - 10 = Received 603 | * - 20 = Enqueued 604 | * - 30 = Processing 605 | * - 40 = Processed 606 | * - 50 = Errored 607 | * 608 | * @enum {integer} 609 | */ 610 | FileProcStatus: -1 | 0 | 10 | 20 | 30 | 40 | 50; 611 | /** 612 | * @description File type 613 | * 1. Road camera (fcamera) 614 | * 2. Driver camera (dcamera) 615 | * 3. Raw log (rlog) 616 | * 4. Qlog 617 | * 5. QCamera 618 | * 6. Wide road camera (extended, ecamera) 619 | * 620 | * @enum {integer} 621 | */ 622 | FileType: 1 | 2 | 3 | 4 | 5 | 6; 623 | /** 624 | * Dongle ID 625 | * @description A unique 16-character hexadecimal string. Can represent a device or a user. 626 | * @example 1a2b3c4d5e6f7a8b 627 | */ 628 | readonly DongleID: string; 629 | /** 630 | * Canonical route name 631 | * @description Contains a dongle ID and timestamp of the beginning of the route 632 | * @example 1a2b3c4d5e6f7a8b|2019-01-01--00-00-00 633 | */ 634 | RouteName: string; 635 | /** 636 | * Canonical segment name 637 | * @description Contains a dongle ID, timestamp of the beginning of the route, and segment number 638 | * @example 1a2b3c4d5e6f7a8b|2019-01-01--00-00-00--0 639 | */ 640 | SegmentName: string; 641 | /** 642 | * Vehicle identification number 643 | * @description 17-character alphanumeric string 644 | * @example 5YJ3E1EA7HF000000 645 | */ 646 | VIN: string; 647 | /** Navigation destination */ 648 | NavigationDestination: { 649 | /** 650 | * @description Short name of destination 651 | * @example 1441 State St 652 | */ 653 | place_name: string; 654 | /** 655 | * @description Address details of destination. Should not include short name. 656 | * @example San Diego, CA 92101, United States 657 | */ 658 | place_details: string; 659 | /** 660 | * @description Latitude, decimal degrees 661 | * @example 32.72045 662 | */ 663 | latitude: number; 664 | /** 665 | * @description Longitude, decimal degrees 666 | * @example -117.16621 667 | */ 668 | longitude: number; 669 | }; 670 | /** 671 | * Navigation saved location ID 672 | * @description Identifier for a saved location 673 | */ 674 | NavigationSavedLocationID: number; 675 | /** Navigation saved location */ 676 | NavigationSavedLocation: ({ 677 | id: components["schemas"]["NavigationSavedLocationID"]; 678 | dongle_id: components["schemas"]["DongleID"]; 679 | save_type: components["schemas"]["NavigationLocationType"]; 680 | /** @description Optional label for locations with type "favorite" */ 681 | label?: string | null; 682 | /** @description When this saved location was last modified */ 683 | modified: string; 684 | }) & components["schemas"]["NavigationDestination"]; 685 | /** 686 | * @description Navigation location type 687 | * @enum {string} 688 | */ 689 | NavigationLocationType: "favorite" | "recent"; 690 | /** 691 | * Clip ID 692 | * @description Unique identifier for a clip 693 | */ 694 | readonly ClipID: number; 695 | ClipProperties: { 696 | id: components["schemas"]["ClipID"]; 697 | /** 698 | * @description Unix timestamp when clip was created, in milliseconds 699 | * @example 1670109391000 700 | */ 701 | create_time: number; 702 | dongle_id: components["schemas"]["DongleID"]; 703 | route_name: components["schemas"]["RouteName"]; 704 | /** @description Unix timestamp when clip starts, in milliseconds */ 705 | start_time: number; 706 | /** @description Unix timestamp when clip ends, in milliseconds */ 707 | end_time: number; 708 | /** @description Optional title for clip */ 709 | title?: string | null; 710 | /** 711 | * @description - `q` = QCamera 712 | * - `f` = Road camera 713 | * - `e` = Wide road camera 714 | * - `d` = Driver camera 715 | * - `360` = 360 video 716 | * 717 | * @enum {string} 718 | */ 719 | video_type: "q" | "f" | "e" | "d" | "360"; 720 | /** @description Clip is publicly accessible */ 721 | is_public?: boolean; 722 | }; 723 | /** Pending Clip */ 724 | PendingClip: components["schemas"]["ClipProperties"] & ({ 725 | /** @enum {string} */ 726 | status?: "pending"; 727 | /** 728 | * @description Pending clip status 729 | * @example processing 730 | * @enum {string} 731 | */ 732 | pending_status?: "waiting_jobs" | "processing"; 733 | /** 734 | * @description Processing progress, from 0 to 1 735 | * @example 0.5 736 | */ 737 | pending_progress?: number; 738 | }); 739 | /** Done Clip */ 740 | DoneClip: components["schemas"]["ClipProperties"] & { 741 | /** @enum {string} */ 742 | status?: "done"; 743 | /** @description URL to clip */ 744 | url?: string; 745 | /** @description URL to clip thumbnail */ 746 | thumbnail?: string; 747 | }; 748 | /** Failed Clip */ 749 | FailedClip: components["schemas"]["ClipProperties"] & ({ 750 | /** @enum {string} */ 751 | status?: "failed"; 752 | /** 753 | * @description Error message 754 | * @example upload_failed 755 | * @enum {string} 756 | */ 757 | error_status?: "upload_failed_request" | "upload_failed" | "upload_failed_dcam" | "upload_timed_out" | "export_failed"; 758 | }); 759 | /** @description Video clip created from part of a route */ 760 | Clip: components["schemas"]["PendingClip"] | components["schemas"]["DoneClip"] | components["schemas"]["FailedClip"]; 761 | JSONRPCPayload: { 762 | /** @description JSON-RPC method name */ 763 | method: string; 764 | /** 765 | * @description JSON-RPC version 766 | * @enum {string} 767 | */ 768 | jsonrpc: "2.0"; 769 | /** 770 | * @description JSON-RPC request ID 771 | * @enum {integer} 772 | */ 773 | id: 0; 774 | }; 775 | JSONRPCResponse: { 776 | /** 777 | * @description JSON-RPC version 778 | * @enum {string} 779 | */ 780 | jsonrpc: "2.0"; 781 | /** 782 | * @description JSON-RPC request ID 783 | * @enum {integer} 784 | */ 785 | id: 0; 786 | /** @description Method-specific result */ 787 | result?: Record; 788 | }; 789 | GetMessageMethod: components["schemas"]["JSONRPCPayload"] & { 790 | /** @enum {string} */ 791 | method?: "getMessage"; 792 | params: { 793 | /** 794 | * @description service name 795 | * @example peripheralState 796 | */ 797 | service: string; 798 | /** 799 | * @description time to wait for a message in milliseconds 800 | * @example 5000 801 | */ 802 | timeout?: number; 803 | }; 804 | }; 805 | GetVersionMethod: components["schemas"]["JSONRPCPayload"] & { 806 | /** @enum {string} */ 807 | method?: "getVersion"; 808 | }; 809 | SetNavDestinationMethod: components["schemas"]["JSONRPCPayload"] & { 810 | /** @enum {string} */ 811 | method?: "setNavDestination"; 812 | params: components["schemas"]["NavigationDestination"]; 813 | }; 814 | RebootMethod: components["schemas"]["JSONRPCPayload"] & { 815 | /** @enum {string} */ 816 | method?: "reboot"; 817 | }; 818 | UploadFilesToUrlsMethod: components["schemas"]["JSONRPCPayload"] & ({ 819 | /** @enum {string} */ 820 | method?: "uploadFilesToUrls"; 821 | params: ({ 822 | fn: string; 823 | url: string; 824 | /** 825 | * @default {} 826 | * @example { 827 | * "x-ms-blob-type": "BlockBlob" 828 | * } 829 | */ 830 | headers?: { 831 | [key: string]: string | undefined; 832 | }; 833 | /** @default false */ 834 | allow_cellular?: boolean; 835 | })[]; 836 | /** 837 | * @description Unix timestamp at which this message will be removed from the offline queue, after 838 | * which it will no longer be sent to the device if it comes online. If not specified, 839 | * this message will not be added to the offline queue. 840 | */ 841 | expiry?: number; 842 | }); 843 | ListUploadQueueMethod: components["schemas"]["JSONRPCPayload"] & { 844 | /** @enum {string} */ 845 | method?: "listUploadQueue"; 846 | }; 847 | GetPublicKeyMethod: components["schemas"]["JSONRPCPayload"] & { 848 | /** @enum {string} */ 849 | method?: "getPublicKey"; 850 | }; 851 | GetSshAuthorizedKeysMethod: components["schemas"]["JSONRPCPayload"] & { 852 | /** @enum {string} */ 853 | method?: "getSshAuthorizedKeys"; 854 | }; 855 | GetSimInfoMethod: components["schemas"]["JSONRPCPayload"] & { 856 | /** @enum {string} */ 857 | method?: "getSimInfo"; 858 | }; 859 | GetNetworkTypeMethod: components["schemas"]["JSONRPCPayload"] & { 860 | /** @enum {string} */ 861 | method?: "getNetworkType"; 862 | }; 863 | GetNetworkMeteredMethod: components["schemas"]["JSONRPCPayload"] & { 864 | /** @enum {string} */ 865 | method?: "getNetworkMetered"; 866 | }; 867 | GetNetworksMethod: components["schemas"]["JSONRPCPayload"] & { 868 | /** @enum {string} */ 869 | method?: "getNetworks"; 870 | }; 871 | TakeSnapshotMethod: components["schemas"]["JSONRPCPayload"] & { 872 | /** @enum {string} */ 873 | method?: "takeSnapshot"; 874 | }; 875 | AthenaPayload: components["schemas"]["GetMessageMethod"] | components["schemas"]["GetVersionMethod"] | components["schemas"]["SetNavDestinationMethod"] | components["schemas"]["RebootMethod"] | components["schemas"]["UploadFilesToUrlsMethod"] | components["schemas"]["ListUploadQueueMethod"] | components["schemas"]["GetPublicKeyMethod"] | components["schemas"]["GetSshAuthorizedKeysMethod"] | components["schemas"]["GetSimInfoMethod"] | components["schemas"]["GetNetworkTypeMethod"] | components["schemas"]["GetNetworkMeteredMethod"] | components["schemas"]["GetNetworksMethod"] | components["schemas"]["TakeSnapshotMethod"]; 876 | /** 877 | * @example { 878 | * "jsonrpc": "2.0", 879 | * "id": 0, 880 | * "result": { 881 | * "success": 1 882 | * } 883 | * } 884 | */ 885 | AthenaResponse: components["schemas"]["JSONRPCResponse"]; 886 | }; 887 | responses: { 888 | /** @description Operation successful */ 889 | SuccessInteger: { 890 | content: { 891 | "application/json": { 892 | /** @constant */ 893 | success: 1; 894 | }; 895 | }; 896 | }; 897 | /** @description Operation successful */ 898 | SuccessBoolean: { 899 | content: { 900 | "application/json": { 901 | /** @constant */ 902 | success: true; 903 | }; 904 | }; 905 | }; 906 | }; 907 | parameters: { 908 | /** @description Dongle ID */ 909 | dongleId: components["schemas"]["DongleID"]; 910 | /** @description Canonical route name */ 911 | routeName: components["schemas"]["RouteName"]; 912 | }; 913 | requestBodies: never; 914 | headers: never; 915 | pathItems: never; 916 | } 917 | 918 | export type external = Record; 919 | 920 | export interface operations { 921 | 922 | getProfile: { 923 | /** 924 | * User profile 925 | * @description Returns information about the authenticated user 926 | */ 927 | responses: { 928 | /** @description JSON object containing the user's profile information */ 929 | 200: { 930 | content: { 931 | "application/json": components["schemas"]["Profile"]; 932 | }; 933 | }; 934 | }; 935 | }; 936 | getDevices: { 937 | /** 938 | * Device list 939 | * @description List devices owned or readable by authenticated user 940 | */ 941 | responses: { 942 | /** @description JSON array of device objects */ 943 | 200: { 944 | content: { 945 | "application/json": (components["schemas"]["Device"])[]; 946 | }; 947 | }; 948 | }; 949 | }; 950 | getUserDevices: { 951 | /** 952 | * List devices (superuser) 953 | * @description List devices owned or readable by specified user 954 | */ 955 | responses: { 956 | /** @description JSON array of device objects */ 957 | 200: { 958 | content: { 959 | "application/json": (components["schemas"]["Device"])[]; 960 | }; 961 | }; 962 | }; 963 | }; 964 | getDevice: { 965 | /** 966 | * Device details 967 | * @description Returns information about the specified device 968 | */ 969 | responses: { 970 | /** @description JSON object containing the device's information */ 971 | 200: { 972 | content: { 973 | "application/json": components["schemas"]["Device"]; 974 | }; 975 | }; 976 | }; 977 | }; 978 | updateDevice: { 979 | /** Update device alias */ 980 | requestBody?: { 981 | content: { 982 | "application/json": { 983 | alias: string; 984 | }; 985 | }; 986 | }; 987 | responses: { 988 | /** @description JSON object containing the updated device's information */ 989 | 200: { 990 | content: { 991 | "application/json": components["schemas"]["Device"]; 992 | }; 993 | }; 994 | }; 995 | }; 996 | getDeviceLocation: { 997 | /** Device location */ 998 | responses: { 999 | /** @description JSON object containing device location, or an error message if the location is not known. */ 1000 | 200: { 1001 | content: { 1002 | "application/json": OneOf<[components["schemas"]["DeviceLocation"] & { 1003 | dongle_id: components["schemas"]["DongleID"]; 1004 | }, { 1005 | /** @enum {string} */ 1006 | error: "Location unavailable"; 1007 | }]>; 1008 | }; 1009 | }; 1010 | }; 1011 | }; 1012 | pairDeviceToUser: { 1013 | /** 1014 | * Pair device 1015 | * @description Pair a device to a user's account. 1016 | */ 1017 | requestBody?: { 1018 | content: { 1019 | "application/json": { 1020 | user_id: components["schemas"]["DongleID"]; 1021 | }; 1022 | }; 1023 | }; 1024 | responses: { 1025 | 200: components["responses"]["SuccessInteger"]; 1026 | }; 1027 | }; 1028 | unpairDevice: { 1029 | /** 1030 | * Unpair device 1031 | * @description Unpair device from authenticated user's account. Any comma prime subscription linked to the device will be cancelled. 1032 | */ 1033 | responses: { 1034 | 200: components["responses"]["SuccessInteger"]; 1035 | }; 1036 | }; 1037 | getDeviceOwner: { 1038 | /** 1039 | * Device owner 1040 | * @description Returns the owner of a device. 1041 | */ 1042 | responses: { 1043 | /** @description JSON object containing information about the device owner */ 1044 | 200: { 1045 | content: { 1046 | "application/json": { 1047 | /** @description OAuth2 user ID */ 1048 | user_id?: string; 1049 | /** @description comma points */ 1050 | points?: number; 1051 | /** @deprecated */ 1052 | username?: string | null; 1053 | email?: string; 1054 | }; 1055 | }; 1056 | }; 1057 | }; 1058 | }; 1059 | getDeviceUsers: { 1060 | /** 1061 | * Device users 1062 | * @description List users with access to a device 1063 | */ 1064 | responses: { 1065 | /** @description JSON array of device user objects */ 1066 | 200: { 1067 | content: { 1068 | "application/json": (components["schemas"]["DeviceUser"])[]; 1069 | }; 1070 | }; 1071 | }; 1072 | }; 1073 | addDeviceUser: { 1074 | /** 1075 | * Grant device access 1076 | * @description Grant read permissions to a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is granted to all users. 1077 | */ 1078 | requestBody?: { 1079 | content: { 1080 | "application/json": { 1081 | /** @description Email of user to add */ 1082 | email?: string; 1083 | /** @description OAuth2 user ID of user to add */ 1084 | email_userid?: string; 1085 | }; 1086 | }; 1087 | }; 1088 | responses: { 1089 | 200: components["responses"]["SuccessInteger"]; 1090 | }; 1091 | }; 1092 | revokeDeviceUser: { 1093 | /** 1094 | * Revoke device access 1095 | * @description Revoke read permissions from a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is removed from all users. 1096 | */ 1097 | requestBody?: { 1098 | content: { 1099 | "application/json": { 1100 | email: string; 1101 | }; 1102 | }; 1103 | }; 1104 | responses: { 1105 | 200: components["responses"]["SuccessInteger"]; 1106 | }; 1107 | }; 1108 | getDeviceStatistics: { 1109 | /** 1110 | * Device driving statistics 1111 | * @description Returns aggregate driving statistics for a device over the past 7 days and all time. 1112 | */ 1113 | responses: { 1114 | /** @description JSON object containing driving statistics */ 1115 | 200: { 1116 | content: { 1117 | "application/json": { 1118 | all: components["schemas"]["DrivingStatistics"]; 1119 | week: components["schemas"]["DrivingStatistics"]; 1120 | }; 1121 | }; 1122 | }; 1123 | }; 1124 | }; 1125 | getDeviceBootLogs: { 1126 | /** 1127 | * Device boot logs 1128 | * @description Retrieve boot logs uploaded from a device. 1129 | */ 1130 | responses: { 1131 | /** @description JSON array of URLs to boot log files. Files are available at each URL for one hour from the time of the request. */ 1132 | 200: { 1133 | content: { 1134 | "application/json": (string)[]; 1135 | }; 1136 | }; 1137 | }; 1138 | }; 1139 | getDeviceCrashLogs: { 1140 | /** 1141 | * Device crash logs 1142 | * @description Retrieve crash logs uploaded from a device. 1143 | */ 1144 | responses: { 1145 | /** @description JSON array of URLs to crash log files. Files are available at each URL for one hour from the time of the request. */ 1146 | 200: { 1147 | content: { 1148 | "application/json": (string)[]; 1149 | }; 1150 | }; 1151 | }; 1152 | }; 1153 | getDeviceRoutes: { 1154 | /** 1155 | * Device routes 1156 | * @description Returns a list of routes uploaded from a device. 1157 | */ 1158 | parameters?: { 1159 | /** @description Maximum number of routes to return */ 1160 | /** @description Return routes created after this timestamp */ 1161 | query?: { 1162 | limit?: number; 1163 | created_after?: number; 1164 | }; 1165 | }; 1166 | responses: { 1167 | /** @description JSON array of route objects */ 1168 | 200: { 1169 | content: { 1170 | "application/json": (components["schemas"]["Route"])[]; 1171 | }; 1172 | }; 1173 | }; 1174 | }; 1175 | getDevicePreservedRoutes: { 1176 | /** 1177 | * Device preserved routes 1178 | * @description Returns a list of preserved routes uploaded from a device. 1179 | */ 1180 | responses: { 1181 | /** @description JSON array of route objects */ 1182 | 200: { 1183 | content: { 1184 | "application/json": (components["schemas"]["Route"])[]; 1185 | }; 1186 | }; 1187 | }; 1188 | }; 1189 | getDeviceRoutesSegments: { 1190 | /** 1191 | * Device routes segments 1192 | * @description Returns a list of route segments uploaded from a device between a start and end timestamp. 1193 | */ 1194 | parameters: { 1195 | /** @description Start timestamp in milliseconds */ 1196 | /** @description End timestamp in milliseconds */ 1197 | query: { 1198 | start: number; 1199 | end: number; 1200 | }; 1201 | }; 1202 | responses: { 1203 | /** @description JSON array of route segment objects */ 1204 | 200: { 1205 | content: { 1206 | "application/json": (components["schemas"]["RouteSegment"])[]; 1207 | }; 1208 | }; 1209 | }; 1210 | }; 1211 | getDeviceSegments: { 1212 | /** 1213 | * Device segments 1214 | * @description Returns time-sorted list of segments, each of which includes basic metadata derived from openpilot logs. 1215 | */ 1216 | parameters: { 1217 | /** @description Start timestamp in milliseconds */ 1218 | /** @description End timestamp in milliseconds. If omitted, the current time is used. */ 1219 | query: { 1220 | from: number; 1221 | to?: number; 1222 | }; 1223 | }; 1224 | responses: { 1225 | /** @description JSON array of segments */ 1226 | 200: { 1227 | content: { 1228 | "application/json": (components["schemas"]["Segment"])[]; 1229 | }; 1230 | }; 1231 | }; 1232 | }; 1233 | submitAthenaPayload: { 1234 | /** 1235 | * Submit Athena payload 1236 | * @description Send JSON-RPC requests to the active websocket connection for a given device, identified by its dongle ID. 1237 | * 1238 | * Some method types support offline queueing of messages until the device comes online (see `expiry`). The 1239 | * response will indicate whether the message was queued or not. 1240 | */ 1241 | /** @description JSON-RPC payload containing athena method name and parameters */ 1242 | requestBody: { 1243 | content: { 1244 | "application/json": components["schemas"]["AthenaPayload"]; 1245 | }; 1246 | }; 1247 | responses: { 1248 | /** @description JSON-RPC response containing the result of the method call */ 1249 | 200: { 1250 | content: { 1251 | "application/json": components["schemas"]["AthenaResponse"]; 1252 | }; 1253 | }; 1254 | }; 1255 | }; 1256 | getDeviceAthenaOfflineQueue: { 1257 | /** 1258 | * Athena offline queue 1259 | * @description Return a list of queued payloads for delivery to device when it is online. 1260 | */ 1261 | responses: { 1262 | /** @description JSON array of queued payloads */ 1263 | 200: { 1264 | content: { 1265 | "application/json": (components["schemas"]["UploadFilesToUrlsMethod"])[]; 1266 | }; 1267 | }; 1268 | }; 1269 | }; 1270 | getUploadUrl: { 1271 | /** 1272 | * Log file upload 1273 | * @description Request a URL to which an openpilot file an be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 1274 | */ 1275 | parameters: { 1276 | /** @description File to upload from openpilot data directory. */ 1277 | /** @description Number of days the url should be valid. */ 1278 | query: { 1279 | path: string; 1280 | expiry_days?: number; 1281 | }; 1282 | }; 1283 | responses: { 1284 | /** @description JSON object containing upload URL */ 1285 | 200: { 1286 | content: { 1287 | "application/json": { 1288 | /** @description URL to which a PUT request can be sent with file contents */ 1289 | url: string; 1290 | }; 1291 | }; 1292 | }; 1293 | }; 1294 | }; 1295 | getUploadUrls: { 1296 | /** 1297 | * Batch log file upload 1298 | * @description Request URLs to which openpilot files can be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 1299 | */ 1300 | requestBody?: { 1301 | content: { 1302 | /** 1303 | * @example { 1304 | * "paths": [ 1305 | * "2019-06-06--11-30-31--9/fcamera.hevc", 1306 | * "2019-06-06--11-30-31--9/ecamera.hevc" 1307 | * ], 1308 | * "expiry_days": 1 1309 | * } 1310 | */ 1311 | "application/json": { 1312 | /** @description Files to upload from openpilot data directory. */ 1313 | paths: (string)[]; 1314 | /** 1315 | * @description number of days the url should be valid 1316 | * @default 1 1317 | */ 1318 | expiry_days?: number; 1319 | }; 1320 | }; 1321 | }; 1322 | responses: { 1323 | /** @description JSON array containing upload URLs */ 1324 | 200: { 1325 | content: { 1326 | "application/json": ({ 1327 | /** @description URL to which a PUT request can be sent with file contents */ 1328 | url: string; 1329 | })[]; 1330 | }; 1331 | }; 1332 | }; 1333 | }; 1334 | pilotPair: { 1335 | /** 1336 | * Pair device 1337 | * @description Pair a device to the authenticated user's account. 1338 | */ 1339 | requestBody?: { 1340 | content: { 1341 | "application/x-www-form-urlencoded": { 1342 | /** @description JWT signed by your device private key. Payload contains `{"identity": , "pair": true}` */ 1343 | pair_token: string; 1344 | }; 1345 | }; 1346 | }; 1347 | responses: { 1348 | /** @description JSON object containing pairing result */ 1349 | 200: { 1350 | content: { 1351 | "application/json": { 1352 | /** @description True if the device was unpaired prior to this call. False if the device was previously paired by an authenticated user. */ 1353 | first_pair: boolean; 1354 | }; 1355 | }; 1356 | }; 1357 | }; 1358 | }; 1359 | pilotAuth: { 1360 | /** Authenticate device (openpilot) */ 1361 | requestBody?: { 1362 | content: { 1363 | "application/json": { 1364 | /** @description Device IMEI */ 1365 | imei: string; 1366 | /** @description Device IMEI, second slot */ 1367 | imei2?: string; 1368 | /** @description Device serial number */ 1369 | serial: string; 1370 | /** @description 2048-bit RSA public key */ 1371 | public_key: string; 1372 | /** @description JWT signed by private key. Payload must contain `{"register": true}`. */ 1373 | register_token: string; 1374 | }; 1375 | }; 1376 | }; 1377 | responses: { 1378 | /** @description JSON object containing authenticated dongle ID and token */ 1379 | 200: { 1380 | content: { 1381 | "application/json": { 1382 | dongle_id: components["schemas"]["DongleID"]; 1383 | /** @description JWT */ 1384 | access_token: string; 1385 | }; 1386 | }; 1387 | }; 1388 | }; 1389 | }; 1390 | getRoute: { 1391 | /** 1392 | * Route details 1393 | * @description Returns information about the specified route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 1394 | */ 1395 | responses: { 1396 | /** @description JSON object containing route information */ 1397 | 200: { 1398 | content: { 1399 | "application/json": components["schemas"]["Route"]; 1400 | }; 1401 | }; 1402 | }; 1403 | }; 1404 | updateRoute: { 1405 | /** 1406 | * Update route 1407 | * @description Update route metadata. Authenticated user must have ownership of the device from which the route was uploaded. 1408 | */ 1409 | requestBody?: { 1410 | content: { 1411 | "application/json": { 1412 | /** 1413 | * @deprecated 1414 | * @description Route rating 1415 | * @example 4 1416 | */ 1417 | rating?: number; 1418 | /** 1419 | * @description Whether the route is publicly accessible 1420 | * @example true 1421 | */ 1422 | is_public?: boolean; 1423 | }; 1424 | }; 1425 | }; 1426 | responses: { 1427 | /** @description JSON object containing updated route information */ 1428 | 200: { 1429 | content: { 1430 | "application/json": components["schemas"]["Route"]; 1431 | }; 1432 | }; 1433 | }; 1434 | }; 1435 | getRouteSegments: { 1436 | /** 1437 | * Route segments 1438 | * @description Returns list of segments comprising a route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 1439 | */ 1440 | responses: { 1441 | /** @description JSON array of segments */ 1442 | 200: { 1443 | content: { 1444 | "application/json": (components["schemas"]["Segment"])[]; 1445 | }; 1446 | }; 1447 | }; 1448 | }; 1449 | getRouteFiles: { 1450 | /** 1451 | * Raw log files 1452 | * @description Retrieve uploaded files for a route. Calls to this API are rate limited to 5 per minute. 1453 | */ 1454 | responses: { 1455 | /** @description JSON object containing signed URLs to various log files. URLs are valid for 1 hour. All arrays are sorted by segment number ascending. */ 1456 | 200: { 1457 | content: { 1458 | "application/json": { 1459 | /** @description Array of signed URLs to qlog.bz2 files */ 1460 | qlogs: (string)[]; 1461 | /** @description Array of signed URLs to qcamera.ts files */ 1462 | qcameras: (string)[]; 1463 | /** @description Array of signed URLs to rlog.bz2 files */ 1464 | logs: (string)[]; 1465 | /** @description Array of signed URLs to fcamera.hevc files */ 1466 | cameras: (string)[]; 1467 | /** @description Array of signed URLs to dcamera.hevc files */ 1468 | dcameras: (string)[]; 1469 | /** @description Array of signed URLs to ecamera.hevc files */ 1470 | ecameras: (string)[]; 1471 | }; 1472 | }; 1473 | }; 1474 | }; 1475 | }; 1476 | getRouteStream: { 1477 | /** 1478 | * Route HLS stream 1479 | * @description Returns rear camera HLS stream index of MPEG-TS fragments. 1480 | */ 1481 | responses: { 1482 | /** @description m3u8 playlist */ 1483 | 200: { 1484 | content: { 1485 | "application/x-mpegURL": string; 1486 | }; 1487 | }; 1488 | }; 1489 | }; 1490 | getRouteShareSignature: { 1491 | /** 1492 | * Route sharing signature 1493 | * @description Return route share URL signature. Expires in 365 days. 1494 | */ 1495 | responses: { 1496 | /** @description JSON object containing route share signature */ 1497 | 200: { 1498 | content: { 1499 | "application/json": { 1500 | /** @description Unix timestamp of expiration */ 1501 | exp: string; 1502 | /** @description Signature */ 1503 | sig: string; 1504 | }; 1505 | }; 1506 | }; 1507 | }; 1508 | }; 1509 | preserveRoute: { 1510 | /** 1511 | * Preserve route 1512 | * @description Preserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 1513 | */ 1514 | responses: { 1515 | 200: components["responses"]["SuccessInteger"]; 1516 | }; 1517 | }; 1518 | unpreserveRoute: { 1519 | /** 1520 | * Unpreserve route 1521 | * @description Unpreserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 1522 | */ 1523 | responses: { 1524 | 200: components["responses"]["SuccessInteger"]; 1525 | }; 1526 | }; 1527 | getMapboxToken: { 1528 | /** 1529 | * Mapbox token 1530 | * @description Returns a Mapbox token for the specified dongle ID. Authenticated user must have ownership of the dongle ID. 1531 | */ 1532 | responses: { 1533 | /** @description JSON object containing Mapbox token */ 1534 | 200: { 1535 | content: { 1536 | "application/json": { 1537 | /** @description Mapbox token */ 1538 | token: string; 1539 | }; 1540 | }; 1541 | }; 1542 | }; 1543 | }; 1544 | setDestination: { 1545 | /** 1546 | * Set nav destination 1547 | * @description Set destination for navigation. Authenticated user must have ownership of the dongle ID. 1548 | */ 1549 | requestBody?: { 1550 | content: { 1551 | "application/json": components["schemas"]["NavigationDestination"]; 1552 | }; 1553 | }; 1554 | responses: { 1555 | /** @description Destination set */ 1556 | 200: { 1557 | content: { 1558 | "application/json": { 1559 | /** @constant */ 1560 | success: true; 1561 | /** @description True if the destination was stored and will be applied when the device is next online. False if the destination was set immediately. */ 1562 | saved_next: boolean; 1563 | }; 1564 | }; 1565 | }; 1566 | }; 1567 | }; 1568 | getNavigationNext: { 1569 | /** 1570 | * Get nav destination 1571 | * @description Retrieve next location from database. This was set on Set destination if the device was offline. Next location is removed from the database after this call or when a new destination is set. 1572 | */ 1573 | responses: { 1574 | /** @description JSON object containing next destination, or null if no destination is set */ 1575 | 200: { 1576 | content: { 1577 | "application/json": components["schemas"]["NavigationDestination"] | "null"; 1578 | }; 1579 | }; 1580 | }; 1581 | }; 1582 | clearNavigationNext: { 1583 | /** 1584 | * Clear nav destination 1585 | * @description Delete next destination from database. 1586 | */ 1587 | responses: { 1588 | /** @description Destination cleared */ 1589 | 200: { 1590 | content: { 1591 | "application/json": { 1592 | /** @constant */ 1593 | success: true; 1594 | deleted: components["schemas"]["NavigationDestination"]; 1595 | }; 1596 | }; 1597 | }; 1598 | }; 1599 | }; 1600 | getNavigationSavedLocations: { 1601 | /** 1602 | * Saved locations 1603 | * @description Retrieve saved locations from database. 1604 | */ 1605 | responses: { 1606 | /** @description JSON object containing saved locations */ 1607 | 200: { 1608 | content: { 1609 | "application/json": (components["schemas"]["NavigationSavedLocation"])[]; 1610 | }; 1611 | }; 1612 | }; 1613 | }; 1614 | saveNavigationLocation: { 1615 | /** Save location */ 1616 | requestBody?: { 1617 | content: { 1618 | "application/json": components["schemas"]["NavigationSavedLocation"]; 1619 | }; 1620 | }; 1621 | responses: { 1622 | 200: components["responses"]["SuccessBoolean"]; 1623 | }; 1624 | }; 1625 | deleteNavigationLocation: { 1626 | /** Delete location */ 1627 | requestBody?: { 1628 | content: { 1629 | "application/json": { 1630 | id?: components["schemas"]["NavigationSavedLocationID"]; 1631 | }; 1632 | }; 1633 | }; 1634 | responses: { 1635 | 200: components["responses"]["SuccessBoolean"]; 1636 | }; 1637 | }; 1638 | updateNavigationLocation: { 1639 | /** Update location */ 1640 | requestBody?: { 1641 | content: { 1642 | "application/json": components["schemas"]["NavigationSavedLocation"] & { 1643 | id?: components["schemas"]["NavigationSavedLocationID"]; 1644 | }; 1645 | }; 1646 | }; 1647 | responses: { 1648 | 200: components["responses"]["SuccessBoolean"]; 1649 | }; 1650 | }; 1651 | createClip: { 1652 | /** 1653 | * Create clip 1654 | * @description Create a clip from a route. 1655 | */ 1656 | requestBody?: { 1657 | content: { 1658 | "application/json": components["schemas"]["ClipProperties"]; 1659 | }; 1660 | }; 1661 | responses: { 1662 | /** @description JSON object containing clip ID, or an error message */ 1663 | 200: { 1664 | content: { 1665 | "application/json": OneOf<[{ 1666 | /** @constant */ 1667 | success: true; 1668 | clip_id: components["schemas"]["ClipID"]; 1669 | }, { 1670 | /** 1671 | * @description Error code 1672 | * @enum {string} 1673 | */ 1674 | error: "too_many_pending"; 1675 | }]>; 1676 | }; 1677 | }; 1678 | }; 1679 | }; 1680 | getClips: { 1681 | /** 1682 | * List clips 1683 | * @description List clips created for the specified device. 1684 | */ 1685 | requestBody?: { 1686 | content: { 1687 | "application/json": { 1688 | dongle_id?: components["schemas"]["DongleID"]; 1689 | }; 1690 | }; 1691 | }; 1692 | responses: { 1693 | /** @description JSON array of clip objects */ 1694 | 200: { 1695 | content: { 1696 | "application/json": components["schemas"]["PendingClip"] | components["schemas"]["DoneClip"] | components["schemas"]["FailedClip"]; 1697 | }; 1698 | }; 1699 | }; 1700 | }; 1701 | getClip: { 1702 | /** Get clip details */ 1703 | requestBody?: { 1704 | content: { 1705 | "application/json": { 1706 | clip_id: components["schemas"]["ClipID"]; 1707 | dongle_id: components["schemas"]["DongleID"]; 1708 | }; 1709 | }; 1710 | }; 1711 | responses: { 1712 | /** @description JSON object containing clip details */ 1713 | 200: { 1714 | content: { 1715 | "application/json": components["schemas"]["PendingClip"] | components["schemas"]["DoneClip"] | components["schemas"]["FailedClip"]; 1716 | }; 1717 | }; 1718 | }; 1719 | }; 1720 | deleteClip: { 1721 | /** Delete clip */ 1722 | requestBody?: { 1723 | content: { 1724 | "application/json": { 1725 | clip_id: components["schemas"]["ClipID"]; 1726 | dongle_id: components["schemas"]["DongleID"]; 1727 | }; 1728 | }; 1729 | }; 1730 | responses: { 1731 | 200: components["responses"]["SuccessBoolean"]; 1732 | }; 1733 | }; 1734 | updateClip: { 1735 | /** Update clip */ 1736 | requestBody?: { 1737 | content: { 1738 | "application/json": { 1739 | clip_id: components["schemas"]["ClipID"]; 1740 | dongle_id: components["schemas"]["DongleID"]; 1741 | /** @description Whether the clip is public or not */ 1742 | is_public: boolean; 1743 | }; 1744 | }; 1745 | }; 1746 | responses: { 1747 | 200: components["responses"]["SuccessBoolean"]; 1748 | }; 1749 | }; 1750 | } 1751 | -------------------------------------------------------------------------------- /openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: comma API 4 | version: 3.1.0 5 | termsOfService: https://comma.ai/terms 6 | license: 7 | name: MIT 8 | url: https://opensource.org/licenses/MIT 9 | x-logo: 10 | url: https://api.comma.ai/images/logo-806c3186.png 11 | altText: comma logo 12 | servers: 13 | - url: 'https://api.comma.ai' 14 | description: Production server 15 | tags: 16 | - name: definitions 17 | description: | 18 | ### Dongle ID 19 | A dongle ID is a 16-character alphanumeric identifier. Each comma device has a unique dongle ID. Authenticated 20 | users also have a dongle ID associated with their profile. For example, `1a2b3c4d5e6f7a8b`. 21 | 22 | ### Segment 23 | A segment is one minute of driving. openpilot rotates log and camera files at this interval. Segments are 24 | numbered in a 0-indexed fashion. 25 | Segment names are of the form `dongle_id|YYYY-MM-DD--HH-MM-SS--N`, where `N` is the segment number. For example, 26 | `1a2b3c4d5e6f7a8b|2019-01-01--00-00-00--0`. 27 | 28 | ### Route 29 | A route is a sequence of segments recorded while the device is "onroad" (between car ignition and off). Route 30 | names are of the form `dongle_id|YYYY-MM-DD--HH-MM-SS`. For example, `1a2b3c4d5e6f7a8b|2019-01-01--00-00-00`. 31 | - name: authentication 32 | description: | 33 | ### Public access 34 | Some endpoints do not require authentication, or provide access to resources which may be marked as public. These 35 | are listed with the `AuthPublic` security scheme. 36 | 37 | ### User auth 38 | Authenticated resources require a header comprised of a JWT and are listed with the `AuthUser` security scheme. A 39 | token can be generated at https://jwt.comma.ai. Include a header formatted like the following in your requests: 40 | ``` 41 | curl -H 'Authorization: JWT {{token}}' https://api.comma.ai/v1/me 42 | ``` 43 | 44 | ### Device auth 45 | Some resources are only accessible to openpilot devices. These endpoints are listed with the `AuthDevice` security 46 | scheme. A device running openpilot may authenticate using the `/v2/pilotauth` endpoint. 47 | - name: account 48 | description: Get information about your account, including the devices you have access to. 49 | - name: devices 50 | description: Manage your devices. 51 | - name: user management 52 | description: Grant and revoke permissions for other users to access your devices. 53 | - name: routes 54 | description: List drives uploaded from your coma device. 55 | - name: logs 56 | description: | 57 | Retrieve files uploaded from your devices. 58 | 59 | ### Log types 60 | For each segment of an openpilot drive, the following files are created: 61 | | Type | Description | 62 | | ---- | ----------- | 63 | | `qcamera.ts` | Compressed road camera | 64 | | `fcamera.hevc` | Road camera uncompressed | 65 | | `dcamera.hevc` | Driver camera uncompressed | 66 | | `ecamera.hevc` | Wide road camera uncompressed | 67 | | `rlog.bz2` | Full openpilot logs - all messages published by every service | 68 | | `qlog.bz2` | Decimated openpilot logs | 69 | 70 | `dcamera.hevc` is not logged unless the "Record and upload driver camera" toggle is enabled. `qcamera.ts` and 71 | `qlog.bz2` files are uploaded automatically when the device is connected to the internet. Other files must be 72 | manually requested from the device - this is managed by the athena service. 73 | - name: athena 74 | description: Communicate with your device in real time. 75 | - name: navigation 76 | description: Send navigation routes to your device. Requires [comma prime](https://comma.ai/prime). 77 | - name: clips 78 | description: Create shareable video clips from your uploaded log files. Requires [comma prime](https://comma.ai/prime). 79 | - name: openpilot auth 80 | description: Endpoints used to pair or authenticate a device running openpilot. 81 | - name: superuser 82 | description: These endpoints are only accessible to [superusers](https://comma.ai/jobs). 83 | paths: 84 | /v1/me: 85 | get: 86 | operationId: getProfile 87 | summary: User profile 88 | description: Returns information about the authenticated user 89 | tags: 90 | - account 91 | security: 92 | - AuthUser: [] 93 | responses: 94 | '200': 95 | description: JSON object containing the user's profile information 96 | content: 97 | application/json: 98 | schema: 99 | $ref: '#/components/schemas/Profile' 100 | /v1/me/devices: 101 | get: 102 | operationId: getDevices 103 | summary: Device list 104 | description: List devices owned or readable by authenticated user 105 | tags: 106 | - account 107 | security: 108 | - AuthUser: [] 109 | responses: 110 | '200': 111 | description: JSON array of device objects 112 | content: 113 | application/json: 114 | schema: 115 | type: array 116 | items: 117 | $ref: '#/components/schemas/Device' 118 | /v1/{dongleId}/devices: 119 | parameters: 120 | - $ref: '#/components/parameters/dongleId' 121 | get: 122 | operationId: getUserDevices 123 | summary: List devices (superuser) 124 | description: List devices owned or readable by specified user 125 | tags: 126 | - superuser 127 | security: 128 | - AuthUser: [] 129 | responses: 130 | '200': 131 | description: JSON array of device objects 132 | content: 133 | application/json: 134 | schema: 135 | type: array 136 | items: 137 | $ref: '#/components/schemas/Device' 138 | /v1.1/devices/{dongleId}: 139 | parameters: 140 | - $ref: '#/components/parameters/dongleId' 141 | get: 142 | operationId: getDevice 143 | summary: Device details 144 | description: Returns information about the specified device 145 | tags: 146 | - devices 147 | security: 148 | - AuthUser: [] 149 | responses: 150 | '200': 151 | description: JSON object containing the device's information 152 | content: 153 | application/json: 154 | schema: 155 | $ref: '#/components/schemas/Device' 156 | /v1/devices/{dongleId}: 157 | parameters: 158 | - $ref: '#/components/parameters/dongleId' 159 | patch: 160 | operationId: updateDevice 161 | summary: Update device alias 162 | tags: 163 | - devices 164 | security: 165 | - AuthUser: [] 166 | requestBody: 167 | content: 168 | application/json: 169 | schema: 170 | type: object 171 | properties: 172 | alias: 173 | type: string 174 | required: 175 | - alias 176 | responses: 177 | '200': 178 | description: JSON object containing the updated device's information 179 | content: 180 | application/json: 181 | schema: 182 | $ref: '#/components/schemas/Device' 183 | /v1/devices/{dongleId}/location: 184 | parameters: 185 | - $ref: '#/components/parameters/dongleId' 186 | get: 187 | operationId: getDeviceLocation 188 | summary: Device location 189 | tags: 190 | - devices 191 | security: 192 | - AuthUser: [] 193 | responses: 194 | '200': 195 | description: JSON object containing device location, or an error message if the location is not known. 196 | content: 197 | application/json: 198 | schema: 199 | oneOf: 200 | - allOf: 201 | - $ref: '#/components/schemas/DeviceLocation' 202 | - type: object 203 | properties: 204 | dongle_id: 205 | $ref: '#/components/schemas/DongleID' 206 | required: 207 | - dongle_id 208 | - type: object 209 | properties: 210 | error: 211 | type: string 212 | enum: 213 | - "Location unavailable" 214 | required: 215 | - error 216 | /v1/devices/{dongleId}/pair: 217 | parameters: 218 | - $ref: '#/components/parameters/dongleId' 219 | post: 220 | operationId: pairDeviceToUser 221 | summary: Pair device 222 | description: Pair a device to a user's account. 223 | tags: 224 | - devices 225 | security: 226 | - AuthUser: [] 227 | requestBody: 228 | content: 229 | application/json: 230 | schema: 231 | type: object 232 | properties: 233 | user_id: 234 | $ref: '#/components/schemas/DongleID' 235 | required: 236 | - user_id 237 | responses: 238 | '200': 239 | $ref: '#/components/responses/SuccessInteger' 240 | /v1/devices/{dongleId}/unpair: 241 | parameters: 242 | - $ref: '#/components/parameters/dongleId' 243 | post: 244 | operationId: unpairDevice 245 | summary: Unpair device 246 | description: Unpair device from authenticated user's account. Any comma prime subscription linked to the device will be cancelled. 247 | tags: 248 | - devices 249 | security: 250 | - AuthUser: [] 251 | responses: 252 | '200': 253 | $ref: '#/components/responses/SuccessInteger' 254 | /v1/devices/{dongleId}/owner: 255 | parameters: 256 | - $ref: '#/components/parameters/dongleId' 257 | get: 258 | operationId: getDeviceOwner 259 | summary: Device owner 260 | description: Returns the owner of a device. 261 | tags: 262 | - devices 263 | security: 264 | - AuthUser: [] 265 | responses: 266 | '200': 267 | description: JSON object containing information about the device owner 268 | content: 269 | application/json: 270 | schema: 271 | type: object 272 | properties: 273 | user_id: 274 | type: string 275 | description: OAuth2 user ID 276 | points: 277 | type: integer 278 | description: comma points 279 | username: 280 | type: string 281 | deprecated: true 282 | nullable: true 283 | email: 284 | type: string 285 | /v1/devices/{dongleId}/users: 286 | parameters: 287 | - $ref: '#/components/parameters/dongleId' 288 | get: 289 | operationId: getDeviceUsers 290 | summary: Device users 291 | description: List users with access to a device 292 | tags: 293 | - user management 294 | security: 295 | - AuthUser: [] 296 | responses: 297 | '200': 298 | description: JSON array of device user objects 299 | content: 300 | application/json: 301 | schema: 302 | type: array 303 | items: 304 | $ref: '#/components/schemas/DeviceUser' 305 | /v1/devices/{dongleId}/add_user: 306 | parameters: 307 | - $ref: '#/components/parameters/dongleId' 308 | post: 309 | operationId: addDeviceUser 310 | summary: Grant device access 311 | description: Grant read permissions to a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is granted to all users. 312 | tags: 313 | - user management 314 | security: 315 | - AuthUser: [] 316 | requestBody: 317 | content: 318 | application/json: 319 | schema: 320 | type: object 321 | properties: 322 | email: 323 | type: string 324 | description: Email of user to add 325 | email_userid: 326 | type: string 327 | description: OAuth2 user ID of user to add 328 | responses: 329 | '200': 330 | $ref: '#/components/responses/SuccessInteger' 331 | /v1/devices/{dongleId}/del_user: 332 | parameters: 333 | - $ref: '#/components/parameters/dongleId' 334 | post: 335 | operationId: revokeDeviceUser 336 | summary: Revoke device access 337 | description: Revoke read permissions from a user by email. Authed user must be device owner to perform. If multiple users are attached to an email address, device access is removed from all users. 338 | tags: 339 | - user management 340 | security: 341 | - AuthUser: [] 342 | requestBody: 343 | content: 344 | application/json: 345 | schema: 346 | type: object 347 | properties: 348 | email: 349 | type: string 350 | required: 351 | - email 352 | responses: 353 | '200': 354 | $ref: '#/components/responses/SuccessInteger' 355 | /v1.1/devices/{dongleId}/stats: 356 | parameters: 357 | - $ref: '#/components/parameters/dongleId' 358 | get: 359 | operationId: getDeviceStatistics 360 | summary: Device driving statistics 361 | description: Returns aggregate driving statistics for a device over the past 7 days and all time. 362 | tags: 363 | - devices 364 | security: 365 | - AuthUser: [] 366 | - AuthDevice: [] 367 | responses: 368 | '200': 369 | description: JSON object containing driving statistics 370 | content: 371 | application/json: 372 | schema: 373 | type: object 374 | properties: 375 | all: 376 | $ref: '#/components/schemas/DrivingStatistics' 377 | week: 378 | $ref: '#/components/schemas/DrivingStatistics' 379 | required: 380 | - all 381 | - week 382 | /v1/devices/{dongleId}/bootlogs: 383 | parameters: 384 | - $ref: '#/components/parameters/dongleId' 385 | get: 386 | operationId: getDeviceBootLogs 387 | summary: Device boot logs 388 | description: Retrieve boot logs uploaded from a device. 389 | tags: 390 | - logs 391 | security: 392 | - AuthUser: [] 393 | responses: 394 | '200': 395 | description: JSON array of URLs to boot log files. Files are available at each URL for one hour from the time of the request. 396 | content: 397 | application/json: 398 | schema: 399 | type: array 400 | items: 401 | type: string 402 | /v1/devices/{dongleId}/crashlogs: 403 | parameters: 404 | - $ref: '#/components/parameters/dongleId' 405 | get: 406 | operationId: getDeviceCrashLogs 407 | summary: Device crash logs 408 | description: Retrieve crash logs uploaded from a device. 409 | tags: 410 | - logs 411 | security: 412 | - AuthUser: [] 413 | responses: 414 | '200': 415 | description: JSON array of URLs to crash log files. Files are available at each URL for one hour from the time of the request. 416 | content: 417 | application/json: 418 | schema: 419 | type: array 420 | items: 421 | type: string 422 | /v1/devices/{dongleId}/routes: 423 | parameters: 424 | - $ref: '#/components/parameters/dongleId' 425 | get: 426 | operationId: getDeviceRoutes 427 | summary: Device routes 428 | description: Returns a list of routes uploaded from a device. 429 | tags: 430 | - routes 431 | security: 432 | - AuthUser: [] 433 | parameters: 434 | - name: limit 435 | in: query 436 | description: Maximum number of routes to return 437 | schema: 438 | type: integer 439 | default: 20 440 | - name: created_after 441 | in: query 442 | description: Return routes created after this timestamp 443 | schema: 444 | type: integer 445 | responses: 446 | '200': 447 | description: JSON array of route objects 448 | content: 449 | application/json: 450 | schema: 451 | type: array 452 | items: 453 | $ref: '#/components/schemas/Route' 454 | /v1/devices/{dongleId}/routes/preserved: 455 | parameters: 456 | - $ref: '#/components/parameters/dongleId' 457 | get: 458 | operationId: getDevicePreservedRoutes 459 | summary: Device preserved routes 460 | description: Returns a list of preserved routes uploaded from a device. 461 | tags: 462 | - routes 463 | security: 464 | - AuthUser: [] 465 | responses: 466 | '200': 467 | description: JSON array of route objects 468 | content: 469 | application/json: 470 | schema: 471 | type: array 472 | items: 473 | $ref: '#/components/schemas/Route' 474 | /v1/devices/{dongleId}/routes_segments: 475 | parameters: 476 | - $ref: '#/components/parameters/dongleId' 477 | get: 478 | operationId: getDeviceRoutesSegments 479 | summary: Device routes segments 480 | description: Returns a list of route segments uploaded from a device between a start and end timestamp. 481 | tags: 482 | - routes 483 | security: 484 | - AuthUser: [] 485 | - AuthPublic: [] 486 | parameters: 487 | - name: start 488 | in: query 489 | description: Start timestamp in milliseconds 490 | required: true 491 | schema: 492 | type: integer 493 | - name: end 494 | in: query 495 | description: End timestamp in milliseconds 496 | required: true 497 | schema: 498 | type: integer 499 | responses: 500 | '200': 501 | description: JSON array of route segment objects 502 | content: 503 | application/json: 504 | schema: 505 | type: array 506 | items: 507 | $ref: '#/components/schemas/RouteSegment' 508 | /v1/devices/{dongleId}/segments: 509 | parameters: 510 | - $ref: '#/components/parameters/dongleId' 511 | get: 512 | operationId: getDeviceSegments 513 | summary: Device segments 514 | description: Returns time-sorted list of segments, each of which includes basic metadata derived from openpilot logs. 515 | tags: 516 | - routes 517 | security: 518 | - AuthUser: [] 519 | - AuthPublic: [] 520 | parameters: 521 | - name: from 522 | description: Start timestamp in milliseconds 523 | in: query 524 | required: true 525 | schema: 526 | type: number 527 | - name: to 528 | description: End timestamp in milliseconds. If omitted, the current time is used. 529 | in: query 530 | schema: 531 | type: number 532 | responses: 533 | '200': 534 | description: JSON array of segments 535 | content: 536 | application/json: 537 | schema: 538 | type: array 539 | items: 540 | $ref: '#/components/schemas/Segment' 541 | /{dongleId}: 542 | servers: 543 | - url: https://athena.comma.ai 544 | parameters: 545 | - $ref: '#/components/parameters/dongleId' 546 | post: 547 | operationId: submitAthenaPayload 548 | summary: Submit Athena payload 549 | description: | 550 | Send JSON-RPC requests to the active websocket connection for a given device, identified by its dongle ID. 551 | 552 | Some method types support offline queueing of messages until the device comes online (see `expiry`). The 553 | response will indicate whether the message was queued or not. 554 | tags: 555 | - athena 556 | security: 557 | - AuthUser: [] 558 | requestBody: 559 | description: JSON-RPC payload containing athena method name and parameters 560 | required: true 561 | content: 562 | application/json: 563 | schema: 564 | $ref: '#/components/schemas/AthenaPayload' 565 | responses: 566 | '200': 567 | description: JSON-RPC response containing the result of the method call 568 | content: 569 | application/json: 570 | schema: 571 | $ref: '#/components/schemas/AthenaResponse' 572 | /v1/devices/{dongleId}/athena_offline_queue: 573 | parameters: 574 | - $ref: '#/components/parameters/dongleId' 575 | get: 576 | operationId: getDeviceAthenaOfflineQueue 577 | summary: Athena offline queue 578 | description: Return a list of queued payloads for delivery to device when it is online. 579 | tags: 580 | - athena 581 | security: 582 | - AuthUser: [] 583 | responses: 584 | '200': 585 | description: JSON array of queued payloads 586 | content: 587 | application/json: 588 | schema: 589 | type: array 590 | items: 591 | oneOf: 592 | - $ref: '#/components/schemas/UploadFilesToUrlsMethod' 593 | /v1.4/{dongleId}/upload_url: 594 | parameters: 595 | - $ref: '#/components/parameters/dongleId' 596 | get: 597 | operationId: getUploadUrl 598 | summary: Log file upload 599 | description: Request a URL to which an openpilot file an be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 600 | tags: 601 | - logs 602 | security: 603 | - AuthUser: [] 604 | - AuthDevice: [] 605 | parameters: 606 | - name: path 607 | in: query 608 | description: File to upload from openpilot data directory. 609 | required: true 610 | schema: 611 | type: string 612 | example: "2019-06-06--11-30-31--9/fcamera.hevc" 613 | - name: expiry_days 614 | in: query 615 | description: Number of days the url should be valid. 616 | schema: 617 | type: integer 618 | minimum: 1 619 | maximum: 30 620 | default: 1 621 | example: 1 622 | responses: 623 | '200': 624 | description: JSON object containing upload URL 625 | content: 626 | application/json: 627 | schema: 628 | type: object 629 | properties: 630 | url: 631 | type: string 632 | description: URL to which a PUT request can be sent with file contents 633 | required: 634 | - url 635 | example: 636 | url: "https://commaincoming.blob.core.windows.net/commaincoming/239e82a1d3c855f2/2019-06-06--11-30-31/9/fcamera.hevc?sr=b&sp=c&sig=cMCrZt5fje7SDXlKcOIjHgA0wEVAol71FL6ac08Q2Iw%3D&sv=2018-03-28&se=2019-06-13T18%3A43%3A01Z" 637 | /v1/{dongleId}/upload_urls: 638 | parameters: 639 | - $ref: '#/components/parameters/dongleId' 640 | post: 641 | operationId: getUploadUrls 642 | summary: Batch log file upload 643 | description: Request URLs to which openpilot files can be uploaded via HTTP PUT. This endpoint only accepts tokens signed with a device private key. 644 | tags: 645 | - logs 646 | security: 647 | - AuthUser: [] 648 | - AuthDevice: [] 649 | requestBody: 650 | content: 651 | application/json: 652 | schema: 653 | type: object 654 | properties: 655 | paths: 656 | type: array 657 | items: 658 | type: string 659 | description: Files to upload from openpilot data directory. 660 | expiry_days: 661 | type: integer 662 | minimum: 1 663 | maximum: 30 664 | default: 1 665 | description: number of days the url should be valid 666 | required: 667 | - paths 668 | example: 669 | paths: 670 | - "2019-06-06--11-30-31--9/fcamera.hevc" 671 | - "2019-06-06--11-30-31--9/ecamera.hevc" 672 | expiry_days: 1 673 | responses: 674 | '200': 675 | description: JSON array containing upload URLs 676 | content: 677 | application/json: 678 | schema: 679 | type: array 680 | items: 681 | type: object 682 | properties: 683 | url: 684 | type: string 685 | description: URL to which a PUT request can be sent with file contents 686 | required: 687 | - url 688 | example: 689 | - url: "https://commaincoming.blob.core.windows.net/commaincoming/239e82a1d3c855f2/2019-06-06--11-30-31/9/fcamera.hevc?sr=b&sp=c&sig=cMCrZt5fje7SDXlKcOIjHgA0wEVAol71FL6ac08Q2Iw%3D&sv=2018-03-28&se=2019-06-13T18%3A43%3A01Z" 690 | - url: "https://commaincoming.blob.core.windows.net/commaincoming/239e82a1d3c855f2/2019-06-06--11-30-31/9/ecamera.hevc?sr=b&sp=c&sig=cMCrZt5fje7SDXlKcOIjHgA0wEVAol71FL6ac08Q2Iw%3D&sv=2018-03-28&se=2019-06-13T18%3A43%3A01Z" 691 | /v2/pilotpair: 692 | post: 693 | operationId: pilotPair 694 | summary: Pair device 695 | description: Pair a device to the authenticated user's account. 696 | tags: 697 | - openpilot auth 698 | security: 699 | - AuthUser: [] 700 | requestBody: 701 | content: 702 | application/x-www-form-urlencoded: 703 | schema: 704 | type: object 705 | properties: 706 | pair_token: 707 | type: string 708 | description: "JWT signed by your device private key. Payload contains `{\"identity\": , \"pair\": true}`" 709 | required: 710 | - pair_token 711 | responses: 712 | '200': 713 | description: JSON object containing pairing result 714 | content: 715 | application/json: 716 | schema: 717 | type: object 718 | properties: 719 | first_pair: 720 | type: boolean 721 | description: True if the device was unpaired prior to this call. False if the device was previously paired by an authenticated user. 722 | required: 723 | - first_pair 724 | /v2/pilotauth: 725 | post: 726 | operationId: pilotAuth 727 | summary: Authenticate device (openpilot) 728 | tags: 729 | - openpilot auth 730 | security: 731 | - AuthPublic: [] 732 | requestBody: 733 | content: 734 | application/json: 735 | schema: 736 | type: object 737 | properties: 738 | imei: 739 | type: string 740 | description: Device IMEI 741 | imei2: 742 | type: string 743 | description: Device IMEI, second slot 744 | serial: 745 | type: string 746 | description: Device serial number 747 | public_key: 748 | type: string 749 | description: 2048-bit RSA public key 750 | register_token: 751 | type: string 752 | description: "JWT signed by private key. Payload must contain `{\"register\": true}`." 753 | required: 754 | - imei 755 | - serial 756 | - public_key 757 | - register_token 758 | responses: 759 | '200': 760 | description: JSON object containing authenticated dongle ID and token 761 | content: 762 | application/json: 763 | schema: 764 | type: object 765 | properties: 766 | dongle_id: 767 | $ref: '#/components/schemas/DongleID' 768 | access_token: 769 | type: string 770 | description: JWT 771 | required: 772 | - dongle_id 773 | - access_token 774 | /v1/route/{routeName}: 775 | parameters: 776 | - $ref: '#/components/parameters/routeName' 777 | get: 778 | operationId: getRoute 779 | summary: Route details 780 | description: Returns information about the specified route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 781 | tags: 782 | - routes 783 | security: 784 | - AuthUser: [] 785 | - AuthPublic: [] 786 | responses: 787 | '200': 788 | description: JSON object containing route information 789 | content: 790 | application/json: 791 | schema: 792 | $ref: '#/components/schemas/Route' 793 | patch: 794 | operationId: updateRoute 795 | summary: Update route 796 | description: Update route metadata. Authenticated user must have ownership of the device from which the route was uploaded. 797 | tags: 798 | - routes 799 | security: 800 | - AuthUser: [] 801 | requestBody: 802 | content: 803 | application/json: 804 | schema: 805 | type: object 806 | properties: 807 | rating: 808 | type: integer 809 | minimum: 1 810 | maximum: 5 811 | description: Route rating 812 | deprecated: true 813 | example: 4 814 | is_public: 815 | type: boolean 816 | description: Whether the route is publicly accessible 817 | example: true 818 | responses: 819 | '200': 820 | description: JSON object containing updated route information 821 | content: 822 | application/json: 823 | schema: 824 | $ref: '#/components/schemas/Route' 825 | /v1/route/{routeName}/segments: 826 | parameters: 827 | - $ref: '#/components/parameters/routeName' 828 | get: 829 | operationId: getRouteSegments 830 | summary: Route segments 831 | description: Returns list of segments comprising a route. Authenticated user must have ownership of, or read access to, the device from which the route was uploaded. 832 | tags: 833 | - routes 834 | security: 835 | - AuthUser: [] 836 | responses: 837 | '200': 838 | description: JSON array of segments 839 | content: 840 | application/json: 841 | schema: 842 | type: array 843 | items: 844 | $ref: '#/components/schemas/Segment' 845 | /v1/route/{routeName}/files: 846 | parameters: 847 | - $ref: '#/components/parameters/routeName' 848 | get: 849 | operationId: getRouteFiles 850 | summary: Raw log files 851 | description: Retrieve uploaded files for a route. Calls to this API are rate limited to 5 per minute. 852 | tags: 853 | - routes 854 | - logs 855 | security: 856 | - AuthUser: [] 857 | - AuthPublic: [] 858 | responses: 859 | '200': 860 | description: JSON object containing signed URLs to various log files. URLs are valid for 1 hour. All arrays are sorted by segment number ascending. 861 | content: 862 | application/json: 863 | schema: 864 | type: object 865 | properties: 866 | qlogs: 867 | type: array 868 | description: Array of signed URLs to qlog.bz2 files 869 | items: 870 | type: string 871 | qcameras: 872 | type: array 873 | description: Array of signed URLs to qcamera.ts files 874 | items: 875 | type: string 876 | logs: 877 | type: array 878 | description: Array of signed URLs to rlog.bz2 files 879 | items: 880 | type: string 881 | cameras: 882 | type: array 883 | description: Array of signed URLs to fcamera.hevc files 884 | items: 885 | type: string 886 | dcameras: 887 | type: array 888 | description: Array of signed URLs to dcamera.hevc files 889 | items: 890 | type: string 891 | ecameras: 892 | type: array 893 | description: Array of signed URLs to ecamera.hevc files 894 | items: 895 | type: string 896 | required: 897 | - qlogs 898 | - qcameras 899 | - logs 900 | - cameras 901 | - dcameras 902 | - ecameras 903 | /v1/route/{routeName}/qcamera.m3u8: 904 | parameters: 905 | - $ref: '#/components/parameters/routeName' 906 | get: 907 | operationId: getRouteStream 908 | summary: Route HLS stream 909 | description: Returns rear camera HLS stream index of MPEG-TS fragments. 910 | tags: 911 | - routes 912 | security: 913 | - AuthUser: [] 914 | - AuthPublic: [] 915 | responses: 916 | '200': 917 | description: m3u8 playlist 918 | content: 919 | application/x-mpegURL: 920 | schema: 921 | type: string 922 | example: | 923 | #EXTM3U 924 | #EXT-X-VERSION:3 925 | #EXT-X-TARGETDURATION:4 926 | #EXT-X-MEDIA-SEQUENCE:0 927 | #EXT-X-PLAYLIST-TYPE:VOD 928 | 929 | #EXTINF:3.049958, 930 | 8_61.ts?v=2 931 | #EXTINF:3.049955, 932 | 69_61.ts?v=2 933 | #EXTINF:3.049955, 934 | 130_61.ts?v=2 935 | #EXTINF:3.049958, 936 | 191_61.ts?v=2 937 | #EXTINF:3.049970, 938 | 252_61.ts?v=2 939 | #EXTINF:3.049955, 940 | 313_61.ts?v=2 941 | #EXTINF:3.050007, 942 | 374_61.ts?v=2 943 | #EXTINF:3.049913, 944 | 435_61.ts?v=2 945 | #EXTINF:3.049942, 946 | 496_61.ts?v=2 947 | #EXTINF:3.049964, 948 | 557_61.ts?v=2 949 | #EXTINF:3.049955, 950 | 618_61.ts?v=2 951 | /v1/route/{routeName}/share_signature: 952 | parameters: 953 | - $ref: '#/components/parameters/routeName' 954 | get: 955 | operationId: getRouteShareSignature 956 | summary: Route sharing signature 957 | description: Return route share URL signature. Expires in 365 days. 958 | tags: 959 | - routes 960 | security: 961 | - AuthUser: [] 962 | responses: 963 | '200': 964 | description: JSON object containing route share signature 965 | content: 966 | application/json: 967 | schema: 968 | type: object 969 | properties: 970 | exp: 971 | type: string 972 | description: Unix timestamp of expiration 973 | sig: 974 | type: string 975 | description: Signature 976 | required: 977 | - exp 978 | - sig 979 | /v1/route/{routeName}/preserve: 980 | parameters: 981 | - $ref: '#/components/parameters/routeName' 982 | post: 983 | operationId: preserveRoute 984 | summary: Preserve route 985 | description: Preserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 986 | tags: 987 | - routes 988 | security: 989 | - AuthUser: [] 990 | responses: 991 | '200': 992 | $ref: '#/components/responses/SuccessInteger' 993 | delete: 994 | operationId: unpreserveRoute 995 | summary: Unpreserve route 996 | description: Unpreserve route from deletion. Authenticated user must have ownership of the device from which the route was uploaded. 997 | tags: 998 | - routes 999 | security: 1000 | - AuthUser: [] 1001 | responses: 1002 | '200': 1003 | $ref: '#/components/responses/SuccessInteger' 1004 | /v1/tokens/mapbox/{dongleId}: 1005 | parameters: 1006 | - $ref: '#/components/parameters/dongleId' 1007 | get: 1008 | operationId: getMapboxToken 1009 | summary: Mapbox token 1010 | description: Returns a Mapbox token for the specified dongle ID. Authenticated user must have ownership of the dongle ID. 1011 | tags: 1012 | - openpilot auth 1013 | security: 1014 | - AuthUser: [] 1015 | - AuthDevice: [] 1016 | responses: 1017 | '200': 1018 | description: JSON object containing Mapbox token 1019 | content: 1020 | application/json: 1021 | schema: 1022 | type: object 1023 | properties: 1024 | token: 1025 | type: string 1026 | description: Mapbox token 1027 | required: 1028 | - token 1029 | /v1/navigation/{dongleId}/set_destination: 1030 | parameters: 1031 | - $ref: '#/components/parameters/dongleId' 1032 | post: 1033 | operationId: setDestination 1034 | summary: Set nav destination 1035 | description: Set destination for navigation. Authenticated user must have ownership of the dongle ID. 1036 | tags: 1037 | - navigation 1038 | security: 1039 | - AuthUser: [] 1040 | requestBody: 1041 | content: 1042 | application/json: 1043 | schema: 1044 | $ref: '#/components/schemas/NavigationDestination' 1045 | responses: 1046 | '200': 1047 | description: Destination set 1048 | content: 1049 | application/json: 1050 | schema: 1051 | type: object 1052 | properties: 1053 | success: 1054 | type: boolean 1055 | const: true 1056 | saved_next: 1057 | type: boolean 1058 | description: True if the destination was stored and will be applied when the device is next online. False if the destination was set immediately. 1059 | required: 1060 | - success 1061 | - saved_next 1062 | /v1/navigation/{dongleId}/next: 1063 | parameters: 1064 | - $ref: '#/components/parameters/dongleId' 1065 | get: 1066 | operationId: getNavigationNext 1067 | summary: Get nav destination 1068 | description: Retrieve next location from database. This was set on Set destination if the device was offline. Next location is removed from the database after this call or when a new destination is set. 1069 | tags: 1070 | - navigation 1071 | security: 1072 | - AuthUser: [] 1073 | - AuthDevice: [] 1074 | responses: 1075 | '200': 1076 | description: JSON object containing next destination, or null if no destination is set 1077 | content: 1078 | application/json: 1079 | schema: 1080 | oneOf: 1081 | - $ref: '#/components/schemas/NavigationDestination' 1082 | - type: string 1083 | enum: 1084 | - "null" 1085 | delete: 1086 | operationId: clearNavigationNext 1087 | summary: Clear nav destination 1088 | description: Delete next destination from database. 1089 | tags: 1090 | - navigation 1091 | security: 1092 | - AuthUser: [] 1093 | - AuthDevice: [] 1094 | responses: 1095 | '200': 1096 | description: Destination cleared 1097 | content: 1098 | application/json: 1099 | schema: 1100 | type: object 1101 | properties: 1102 | success: 1103 | type: boolean 1104 | const: true 1105 | deleted: 1106 | $ref: '#/components/schemas/NavigationDestination' 1107 | required: 1108 | - success 1109 | - deleted 1110 | /v1/navigation/{dongleId}/locations: 1111 | parameters: 1112 | - $ref: '#/components/parameters/dongleId' 1113 | get: 1114 | operationId: getNavigationSavedLocations 1115 | summary: Saved locations 1116 | description: Retrieve saved locations from database. 1117 | tags: 1118 | - navigation 1119 | security: 1120 | - AuthUser: [] 1121 | - AuthDevice: [] 1122 | responses: 1123 | '200': 1124 | description: JSON object containing saved locations 1125 | content: 1126 | application/json: 1127 | schema: 1128 | type: array 1129 | items: 1130 | $ref: '#/components/schemas/NavigationSavedLocation' 1131 | put: 1132 | operationId: saveNavigationLocation 1133 | summary: Save location 1134 | tags: 1135 | - navigation 1136 | security: 1137 | - AuthUser: [] 1138 | - AuthDevice: [] 1139 | requestBody: 1140 | content: 1141 | application/json: 1142 | schema: 1143 | $ref: '#/components/schemas/NavigationSavedLocation' 1144 | responses: 1145 | '200': 1146 | $ref: '#/components/responses/SuccessBoolean' 1147 | patch: 1148 | operationId: updateNavigationLocation 1149 | summary: Update location 1150 | tags: 1151 | - navigation 1152 | security: 1153 | - AuthUser: [] 1154 | - AuthDevice: [] 1155 | requestBody: 1156 | content: 1157 | application/json: 1158 | schema: 1159 | allOf: 1160 | - $ref: '#/components/schemas/NavigationSavedLocation' 1161 | - type: object 1162 | properties: 1163 | id: 1164 | $ref: '#/components/schemas/NavigationSavedLocationID' 1165 | responses: 1166 | '200': 1167 | $ref: '#/components/responses/SuccessBoolean' 1168 | delete: 1169 | operationId: deleteNavigationLocation 1170 | summary: Delete location 1171 | tags: 1172 | - navigation 1173 | security: 1174 | - AuthUser: [] 1175 | - AuthDevice: [] 1176 | requestBody: 1177 | content: 1178 | application/json: 1179 | schema: 1180 | type: object 1181 | properties: 1182 | id: 1183 | $ref: '#/components/schemas/NavigationSavedLocationID' 1184 | responses: 1185 | '200': 1186 | $ref: '#/components/responses/SuccessBoolean' 1187 | /v1/clips/create: 1188 | post: 1189 | operationId: createClip 1190 | summary: Create clip 1191 | description: Create a clip from a route. 1192 | tags: 1193 | - clips 1194 | security: 1195 | - AuthUser: [] 1196 | requestBody: 1197 | content: 1198 | application/json: 1199 | schema: 1200 | $ref: '#/components/schemas/ClipProperties' 1201 | responses: 1202 | '200': 1203 | description: JSON object containing clip ID, or an error message 1204 | content: 1205 | application/json: 1206 | schema: 1207 | oneOf: 1208 | - type: object 1209 | properties: 1210 | success: 1211 | type: boolean 1212 | const: true 1213 | clip_id: 1214 | $ref: '#/components/schemas/ClipID' 1215 | required: 1216 | - success 1217 | - clip_id 1218 | - type: object 1219 | properties: 1220 | error: 1221 | type: string 1222 | description: Error code 1223 | enum: 1224 | - "too_many_pending" 1225 | required: 1226 | - error 1227 | /v1/clips/list: 1228 | get: 1229 | operationId: getClips 1230 | summary: List clips 1231 | description: List clips created for the specified device. 1232 | tags: 1233 | - clips 1234 | security: 1235 | - AuthUser: [] 1236 | requestBody: 1237 | content: 1238 | application/json: 1239 | schema: 1240 | type: object 1241 | properties: 1242 | dongle_id: 1243 | $ref: '#/components/schemas/DongleID' 1244 | responses: 1245 | '200': 1246 | description: JSON array of clip objects 1247 | content: 1248 | application/json: 1249 | schema: 1250 | type: object 1251 | oneOf: 1252 | - $ref: '#/components/schemas/PendingClip' 1253 | - $ref: '#/components/schemas/DoneClip' 1254 | - $ref: '#/components/schemas/FailedClip' 1255 | /v1/clips/details: 1256 | get: 1257 | operationId: getClip 1258 | summary: Get clip details 1259 | tags: 1260 | - clips 1261 | security: 1262 | - AuthUser: [] 1263 | - AuthPublic: [] 1264 | requestBody: 1265 | content: 1266 | application/json: 1267 | schema: 1268 | type: object 1269 | properties: 1270 | clip_id: 1271 | $ref: '#/components/schemas/ClipID' 1272 | dongle_id: 1273 | $ref: '#/components/schemas/DongleID' 1274 | required: 1275 | - clip_id 1276 | - dongle_id 1277 | responses: 1278 | '200': 1279 | description: JSON object containing clip details 1280 | content: 1281 | application/json: 1282 | schema: 1283 | type: object 1284 | oneOf: 1285 | - $ref: '#/components/schemas/PendingClip' 1286 | - $ref: '#/components/schemas/DoneClip' 1287 | - $ref: '#/components/schemas/FailedClip' 1288 | /v1/clips/update: 1289 | patch: 1290 | operationId: updateClip 1291 | summary: Update clip 1292 | tags: 1293 | - clips 1294 | security: 1295 | - AuthUser: [] 1296 | requestBody: 1297 | content: 1298 | application/json: 1299 | schema: 1300 | type: object 1301 | properties: 1302 | clip_id: 1303 | $ref: '#/components/schemas/ClipID' 1304 | dongle_id: 1305 | $ref: '#/components/schemas/DongleID' 1306 | is_public: 1307 | type: boolean 1308 | description: Whether the clip is public or not 1309 | required: 1310 | - clip_id 1311 | - dongle_id 1312 | - is_public 1313 | responses: 1314 | '200': 1315 | $ref: '#/components/responses/SuccessBoolean' 1316 | delete: 1317 | operationId: deleteClip 1318 | summary: Delete clip 1319 | tags: 1320 | - clips 1321 | security: 1322 | - AuthUser: [] 1323 | requestBody: 1324 | content: 1325 | application/json: 1326 | schema: 1327 | type: object 1328 | properties: 1329 | clip_id: 1330 | $ref: '#/components/schemas/ClipID' 1331 | dongle_id: 1332 | $ref: '#/components/schemas/DongleID' 1333 | required: 1334 | - clip_id 1335 | - dongle_id 1336 | responses: 1337 | '200': 1338 | $ref: '#/components/responses/SuccessBoolean' 1339 | components: 1340 | securitySchemes: 1341 | AuthUser: 1342 | summary: Authenticated User 1343 | description: Generate a JWT using the service at https://jwt.comma.ai. 1344 | type: http 1345 | scheme: bearer 1346 | bearerFormat: JWT 1347 | AuthDevice: 1348 | summary: Authenticated Device 1349 | description: Device can authenticate using the `/v2/pilotauth` endpoint. 1350 | type: http 1351 | scheme: bearer 1352 | bearerFormat: JWT 1353 | responses: 1354 | SuccessInteger: 1355 | description: Operation successful 1356 | content: 1357 | application/json: 1358 | schema: 1359 | properties: 1360 | success: 1361 | type: integer 1362 | const: 1 1363 | required: 1364 | - success 1365 | SuccessBoolean: 1366 | description: Operation successful 1367 | content: 1368 | application/json: 1369 | schema: 1370 | properties: 1371 | success: 1372 | type: boolean 1373 | const: true 1374 | required: 1375 | - success 1376 | schemas: 1377 | Profile: 1378 | type: object 1379 | properties: 1380 | email: 1381 | type: string 1382 | format: email 1383 | description: Email address 1384 | example: "commaphone3@gmail.com" 1385 | id: 1386 | type: string 1387 | description: Dongle ID 1388 | example: "2e9eeac96ea4e6a6" 1389 | points: 1390 | type: integer 1391 | description: comma points 1392 | deprecated: true 1393 | example: 34933 1394 | regdate: 1395 | type: integer 1396 | description: Unix timestamp at time of registration 1397 | example: 1465103707 1398 | superuser: 1399 | type: boolean 1400 | description: Apply for superuser here 1401 | example: false 1402 | username: 1403 | type: string 1404 | description: Username 1405 | deprecated: true 1406 | nullable: true 1407 | example: "joeyjoejoe" 1408 | user_id: 1409 | type: string 1410 | description: OAuth2 user ID 1411 | example: google_111803823964622526972 1412 | required: 1413 | - email 1414 | - id 1415 | - points 1416 | - regdate 1417 | - superuser 1418 | - user_id 1419 | Device: 1420 | type: object 1421 | properties: 1422 | dongle_id: 1423 | $ref: '#/components/schemas/DongleID' 1424 | readOnly: true 1425 | alias: 1426 | type: string 1427 | readOnly: true 1428 | description: Device nickname 1429 | serial: 1430 | type: string 1431 | readOnly: true 1432 | description: Device serial number 1433 | athena_host: 1434 | type: string 1435 | nullable: true 1436 | readOnly: true 1437 | description: Last connected athena server hostname 1438 | last_athena_ping: 1439 | type: integer 1440 | readOnly: true 1441 | description: Unix timestamp of last athena ping 1442 | ignore_uploads: 1443 | type: boolean 1444 | nullable: true 1445 | readOnly: true 1446 | description: Uploads are ignored from this device 1447 | is_paired: 1448 | type: boolean 1449 | readOnly: true 1450 | description: Device has an owner 1451 | is_owner: 1452 | type: boolean 1453 | readOnly: true 1454 | description: Authenticated user has write-access to the device 1455 | public_key: 1456 | type: string 1457 | nullable: true 1458 | readOnly: true 1459 | description: 2048-bit public RSA key 1460 | prime: 1461 | type: boolean 1462 | readOnly: true 1463 | description: Device has a prime subscription 1464 | prime_type: 1465 | type: number 1466 | enum: 1467 | - 0 1468 | - 1 1469 | - 2 1470 | - 3 1471 | - 4 1472 | readOnly: true 1473 | description: | 1474 | Prime subscription type 1475 | - 0 = None 1476 | - 1 = Magenta 1477 | - 2 = Lite 1478 | - 3 = Blue 1479 | - 4 = Magenta New 1480 | trial_claimed: 1481 | type: boolean 1482 | nullable: true 1483 | description: Device prime trial is claimed 1484 | readOnly: true 1485 | device_type: 1486 | type: string 1487 | enum: 1488 | - app 1489 | - neo 1490 | - panda 1491 | - two 1492 | - freon 1493 | - pc 1494 | - three 1495 | readOnly: true 1496 | description: Device type 1497 | last_gps_time: 1498 | type: integer 1499 | nullable: true 1500 | readOnly: true 1501 | description: Unix timestamp, in milliseconds 1502 | last_gps_lat: 1503 | type: number 1504 | nullable: true 1505 | readOnly: true 1506 | description: Latitude, in decimal degrees 1507 | last_gps_lng: 1508 | type: number 1509 | nullable: true 1510 | readOnly: true 1511 | description: Longitude, in decimal degrees 1512 | last_gps_accuracy: 1513 | type: number 1514 | nullable: true 1515 | readOnly: true 1516 | description: Accuracy, in metres 1517 | last_gps_speed: 1518 | type: number 1519 | nullable: true 1520 | readOnly: true 1521 | description: Speed, in metres per second 1522 | last_gps_bearing: 1523 | type: number 1524 | nullable: true 1525 | readOnly: true 1526 | description: Direction angle, in degrees from north 1527 | openpilot_version: 1528 | type: string 1529 | nullable: true 1530 | readOnly: true 1531 | description: openpilot version 1532 | sim_id: 1533 | type: string 1534 | nullable: true 1535 | readOnly: true 1536 | description: Last known SIM ID of the device 1537 | required: 1538 | - dongle_id 1539 | - alias 1540 | - serial 1541 | - is_paired 1542 | - public_key 1543 | - prime 1544 | - prime_type 1545 | - trial_claimed 1546 | - device_type 1547 | - last_gps_time 1548 | - last_gps_lat 1549 | - last_gps_lng 1550 | - last_gps_accuracy 1551 | - last_gps_speed 1552 | - last_gps_bearing 1553 | - sim_id 1554 | DeviceUser: 1555 | type: object 1556 | properties: 1557 | email: 1558 | type: string 1559 | description: User email 1560 | permission: 1561 | $ref: '#/components/schemas/DeviceUserPermission' 1562 | required: 1563 | - email 1564 | - permission 1565 | DeviceUserPermission: 1566 | type: string 1567 | description: Device user permission 1568 | enum: 1569 | - read_access 1570 | - owner 1571 | DeviceLocation: 1572 | type: object 1573 | properties: 1574 | lat: 1575 | type: number 1576 | description: Latitude, in decimal degrees 1577 | lng: 1578 | type: number 1579 | description: Longitude, in decimal degrees 1580 | time: 1581 | type: integer 1582 | description: Unix timestamp, in milliseconds 1583 | accuracy: 1584 | type: number 1585 | description: Accuracy, in metres 1586 | speed: 1587 | type: number 1588 | description: Speed, in metres per second 1589 | bearing: 1590 | type: number 1591 | description: Direction angle, in degrees from north 1592 | required: 1593 | - lat 1594 | - lng 1595 | - time 1596 | - accuracy 1597 | - speed 1598 | - bearing 1599 | DrivingStatistics: 1600 | type: object 1601 | description: Summary of drives over a period of time 1602 | properties: 1603 | distance: 1604 | type: number 1605 | description: Sum of distance driven in time period, in miles 1606 | minutes: 1607 | type: integer 1608 | description: Sum of time driven in time period, in minutes 1609 | routes: 1610 | type: integer 1611 | description: Count of routes in time period 1612 | required: 1613 | - distance 1614 | - minutes 1615 | - routes 1616 | Segment: 1617 | type: object 1618 | description: A single segment of a route is up to 60 seconds in length. 1619 | properties: 1620 | canonical_name: 1621 | $ref: '#/components/schemas/SegmentName' 1622 | number: 1623 | type: integer 1624 | description: Segment number 1625 | minimum: 0 1626 | canonical_route_name: 1627 | $ref: '#/components/schemas/RouteName' 1628 | dongle_id: 1629 | $ref: '#/components/schemas/DongleID' 1630 | create_time: 1631 | type: integer 1632 | description: Unix timestamp at which upload_url was first called for file in segment 1633 | start_time_utc_millis: 1634 | type: integer 1635 | description: Segment start time, milliseconds since epoch 1636 | end_time_utc_millis: 1637 | type: integer 1638 | description: Segment end time, milliseconds since epoch 1639 | url: 1640 | type: string 1641 | description: Signed URL from which route.coords and JPEGs can be downloaded 1642 | length: 1643 | type: number 1644 | description: Sum of distances between GPS points in miles 1645 | can: 1646 | type: boolean 1647 | description: Segment contains CAN messages 1648 | hpgps: 1649 | type: boolean 1650 | description: Segment has ublox packets 1651 | radar: 1652 | type: boolean 1653 | description: Segment contains radar tracks in CAN 1654 | devicetype: 1655 | $ref: '#/components/schemas/SegmentDataSource' 1656 | proc_log: 1657 | $ref: '#/components/schemas/FileProcStatus' 1658 | proc_qlog: 1659 | $ref: '#/components/schemas/FileProcStatus' 1660 | proc_camera: 1661 | $ref: '#/components/schemas/FileProcStatus' 1662 | proc_dcamera: 1663 | $ref: '#/components/schemas/FileProcStatus' 1664 | passive: 1665 | type: boolean 1666 | description: openpilot is running in passive mode 1667 | version: 1668 | type: string 1669 | description: openpilot version 1670 | git_commit: 1671 | type: string 1672 | description: git commit hash 1673 | git_branch: 1674 | type: string 1675 | description: git branch 1676 | git_remote: 1677 | type: string 1678 | description: git remote url 1679 | git_dirty: 1680 | type: boolean 1681 | description: git working tree is dirty 1682 | required: 1683 | - canonical_name 1684 | - number 1685 | - canonical_route_name 1686 | - dongle_id 1687 | - create_time 1688 | - start_time_utc_millis 1689 | - end_time_utc_millis 1690 | - url 1691 | - length 1692 | - can 1693 | - hpgps 1694 | - radar 1695 | - devicetype 1696 | - proc_log 1697 | - proc_qlog 1698 | - proc_camera 1699 | - proc_dcamera 1700 | - passive 1701 | - version 1702 | - git_commit 1703 | - git_branch 1704 | - git_remote 1705 | - git_dirty 1706 | Route: 1707 | type: object 1708 | properties: 1709 | fullname: 1710 | $ref: '#/components/schemas/RouteName' 1711 | dongle_id: 1712 | $ref: '#/components/schemas/DongleID' 1713 | user_id: 1714 | $ref: '#/components/schemas/DongleID' 1715 | is_public: 1716 | type: boolean 1717 | description: Route is publicly accessible 1718 | create_time: 1719 | type: integer 1720 | description: Unix timestamp at which upload_url was first called for file in route 1721 | url: 1722 | type: string 1723 | description: Signed storage bucket URL from which route.coords and JPEGs can be downloaded 1724 | share_expiry: 1725 | type: integer 1726 | description: Unix timestamp at which signed URL expires 1727 | share_sig: 1728 | type: string 1729 | description: URL signature 1730 | length: 1731 | type: number 1732 | description: Sum of distances between GPS points in miles 1733 | can: 1734 | type: boolean 1735 | description: Route contains CAN messages 1736 | hpgps: 1737 | type: boolean 1738 | description: Route has ublox packets 1739 | radar: 1740 | type: boolean 1741 | description: Route contains radar tracks in CAN 1742 | devicetype: 1743 | $ref: '#/components/schemas/SegmentDataSource' 1744 | maxqlog: 1745 | type: integer 1746 | description: Maximum qlog segment number uploaded 1747 | maxqcamera: 1748 | type: integer 1749 | description: Maximum qcamera segment number uploaded 1750 | maxlog: 1751 | type: integer 1752 | description: Maximum log segment number uploaded 1753 | maxcamera: 1754 | type: integer 1755 | description: Maximum road camera segment number uploaded 1756 | maxdcamera: 1757 | type: integer 1758 | description: Maximum driver camera segment number uploaded 1759 | maxecamera: 1760 | type: integer 1761 | description: Maximum wide road camera segment number uploaded 1762 | procqlog: 1763 | type: integer 1764 | description: Maximum qlog segment number processed 1765 | procqcamera: 1766 | type: integer 1767 | description: Maximum qcamera segment number processed 1768 | proclog: 1769 | type: integer 1770 | description: Maximum log segment number processed 1771 | proccamera: 1772 | type: integer 1773 | description: Maximum road camera segment number processed 1774 | start_lat: 1775 | type: number 1776 | description: First latitude recorded in route from GPS 1777 | start_lng: 1778 | type: number 1779 | description: First longitude recorded in route from GPS 1780 | start_time: 1781 | type: number 1782 | description: Unix timestamp at beginning of route 1783 | end_lat: 1784 | type: number 1785 | description: Last latitude recorded in route from GPS 1786 | end_lng: 1787 | type: number 1788 | description: Last longitude recorded in route from GPS 1789 | end_time: 1790 | type: number 1791 | description: Unix timestamp at end of last segment in route 1792 | passive: 1793 | type: boolean 1794 | description: openpilot is running in passive mode 1795 | version: 1796 | type: string 1797 | description: openpilot version 1798 | git_commit: 1799 | type: string 1800 | description: git commit hash 1801 | git_branch: 1802 | type: string 1803 | description: git branch 1804 | git_remote: 1805 | type: string 1806 | description: git remote url 1807 | git_dirty: 1808 | type: boolean 1809 | description: git working tree is dirty 1810 | platform: 1811 | type: string 1812 | description: openpilot platform name 1813 | vin: 1814 | $ref: '#/components/schemas/VIN' 1815 | init_logmonotime: 1816 | type: integer 1817 | description: Minimum logMonoTime from openpilot log 1818 | required: 1819 | - fullname 1820 | - dongle_id 1821 | - user_id 1822 | - create_time 1823 | - url 1824 | - share_expiry 1825 | - share_sig 1826 | - length 1827 | - devicetype 1828 | - maxlog 1829 | - maxcamera 1830 | - maxdcamera 1831 | - proclog 1832 | - proccamera 1833 | - start_time 1834 | - end_time 1835 | RouteSegment: 1836 | type: object 1837 | allOf: 1838 | - $ref: '#/components/schemas/Route' 1839 | - type: object 1840 | properties: 1841 | segment_numbers: 1842 | type: array 1843 | description: Segment numbers in route 1844 | items: 1845 | type: integer 1846 | minimum: 0 1847 | segment_start_times: 1848 | type: array 1849 | description: Segment start times in milliseconds since epoch 1850 | items: 1851 | type: integer 1852 | segment_end_times: 1853 | type: array 1854 | description: Segment end times in milliseconds since epoch 1855 | items: 1856 | type: integer 1857 | required: 1858 | - segment_numbers 1859 | - segment_start_times 1860 | - segment_end_times 1861 | DeviceType: 1862 | type: string 1863 | description: Device type 1864 | enum: 1865 | - app 1866 | - neo 1867 | - panda 1868 | - two 1869 | - freon 1870 | - pc 1871 | - three 1872 | SegmentDataSource: 1873 | type: integer 1874 | description: | 1875 | Data source 1876 | - 3 = eon 1877 | - 6 = comma two 1878 | - 7 = comma three 1879 | enum: 1880 | - 3 1881 | - 6 1882 | - 7 1883 | FileProcStatus: 1884 | type: integer 1885 | description: | 1886 | Log file status 1887 | - -1 = Not received 1888 | - 0 = Upload URL sent 1889 | - 10 = Received 1890 | - 20 = Enqueued 1891 | - 30 = Processing 1892 | - 40 = Processed 1893 | - 50 = Errored 1894 | enum: 1895 | - -1 1896 | - 0 1897 | - 10 1898 | - 20 1899 | - 30 1900 | - 40 1901 | - 50 1902 | FileType: 1903 | type: integer 1904 | description: | 1905 | File type 1906 | 1. Road camera (fcamera) 1907 | 2. Driver camera (dcamera) 1908 | 3. Raw log (rlog) 1909 | 4. Qlog 1910 | 5. QCamera 1911 | 6. Wide road camera (extended, ecamera) 1912 | enum: 1913 | - 1 1914 | - 2 1915 | - 3 1916 | - 4 1917 | - 5 1918 | - 6 1919 | DongleID: 1920 | title: Dongle ID 1921 | description: A unique 16-character hexadecimal string. Can represent a device or a user. 1922 | type: string 1923 | pattern: '^[0-9a-f]{16}$' 1924 | readOnly: true 1925 | example: "1a2b3c4d5e6f7a8b" 1926 | RouteName: 1927 | title: Canonical route name 1928 | description: Contains a dongle ID and timestamp of the beginning of the route 1929 | type: string 1930 | pattern: '^[0-9a-f]{16}\|[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}$' 1931 | example: "1a2b3c4d5e6f7a8b|2019-01-01--00-00-00" 1932 | SegmentName: 1933 | title: Canonical segment name 1934 | description: Contains a dongle ID, timestamp of the beginning of the route, and segment number 1935 | type: string 1936 | pattern: '^[0-9a-f]{16}\|[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+$' 1937 | example: "1a2b3c4d5e6f7a8b|2019-01-01--00-00-00--0" 1938 | VIN: 1939 | title: Vehicle identification number 1940 | description: 17-character alphanumeric string 1941 | type: string 1942 | pattern: '^[0-9A-Z]{17}$' 1943 | example: "5YJ3E1EA7HF000000" 1944 | NavigationDestination: 1945 | title: Navigation destination 1946 | type: object 1947 | properties: 1948 | place_name: 1949 | type: string 1950 | description: Short name of destination 1951 | example: "1441 State St" 1952 | place_details: 1953 | type: string 1954 | description: Address details of destination. Should not include short name. 1955 | example: "San Diego, CA 92101, United States" 1956 | latitude: 1957 | type: number 1958 | description: Latitude, decimal degrees 1959 | example: 32.720450 1960 | longitude: 1961 | type: number 1962 | description: Longitude, decimal degrees 1963 | example: -117.166210 1964 | required: 1965 | - place_name 1966 | - place_details 1967 | - latitude 1968 | - longitude 1969 | NavigationSavedLocationID: 1970 | title: Navigation saved location ID 1971 | description: Identifier for a saved location 1972 | type: number 1973 | NavigationSavedLocation: 1974 | title: Navigation saved location 1975 | type: object 1976 | allOf: 1977 | - type: object 1978 | properties: 1979 | id: 1980 | $ref: '#/components/schemas/NavigationSavedLocationID' 1981 | readOnly: true 1982 | dongle_id: 1983 | $ref: '#/components/schemas/DongleID' 1984 | readOnly: true 1985 | save_type: 1986 | $ref: '#/components/schemas/NavigationLocationType' 1987 | label: 1988 | type: string 1989 | description: Optional label for locations with type "favorite" 1990 | nullable: true 1991 | modified: 1992 | type: string 1993 | description: When this saved location was last modified 1994 | readOnly: true 1995 | required: 1996 | - id 1997 | - dongle_id 1998 | - save_type 1999 | - modified 2000 | - $ref: '#/components/schemas/NavigationDestination' 2001 | NavigationLocationType: 2002 | type: string 2003 | description: Navigation location type 2004 | enum: 2005 | - favorite 2006 | - recent 2007 | ClipID: 2008 | title: Clip ID 2009 | description: Unique identifier for a clip 2010 | type: integer 2011 | readOnly: true 2012 | ClipProperties: 2013 | type: object 2014 | properties: 2015 | id: 2016 | $ref: '#/components/schemas/ClipID' 2017 | create_time: 2018 | description: Unix timestamp when clip was created, in milliseconds 2019 | type: integer 2020 | readOnly: true 2021 | example: 1670109391000 2022 | dongle_id: 2023 | $ref: '#/components/schemas/DongleID' 2024 | route_name: 2025 | $ref: '#/components/schemas/RouteName' 2026 | start_time: 2027 | type: integer 2028 | description: Unix timestamp when clip starts, in milliseconds 2029 | end_time: 2030 | type: integer 2031 | description: Unix timestamp when clip ends, in milliseconds 2032 | title: 2033 | type: string 2034 | description: Optional title for clip 2035 | nullable: true 2036 | maxLength: 128 2037 | video_type: 2038 | description: | 2039 | - `q` = QCamera 2040 | - `f` = Road camera 2041 | - `e` = Wide road camera 2042 | - `d` = Driver camera 2043 | - `360` = 360 video 2044 | type: string 2045 | enum: 2046 | - q 2047 | - f 2048 | - e 2049 | - d 2050 | - "360" 2051 | is_public: 2052 | type: boolean 2053 | description: Clip is publicly accessible 2054 | required: 2055 | - id 2056 | - create_time 2057 | - dongle_id 2058 | - route_name 2059 | - start_time 2060 | - end_time 2061 | - video_type 2062 | PendingClip: 2063 | title: Pending Clip 2064 | type: object 2065 | allOf: 2066 | - $ref: '#/components/schemas/ClipProperties' 2067 | - type: object 2068 | properties: 2069 | status: 2070 | type: string 2071 | enum: 2072 | - pending 2073 | pending_status: 2074 | description: Pending clip status 2075 | type: string 2076 | enum: 2077 | - waiting_jobs 2078 | - processing 2079 | readOnly: true 2080 | example: processing 2081 | pending_progress: 2082 | description: Processing progress, from 0 to 1 2083 | type: number 2084 | minimum: 0 2085 | maximum: 1 2086 | readOnly: true 2087 | example: 0.5 2088 | required: 2089 | - status 2090 | - is_public 2091 | - title 2092 | DoneClip: 2093 | title: Done Clip 2094 | type: object 2095 | allOf: 2096 | - $ref: '#/components/schemas/ClipProperties' 2097 | - type: object 2098 | properties: 2099 | status: 2100 | type: string 2101 | enum: 2102 | - done 2103 | url: 2104 | type: string 2105 | description: URL to clip 2106 | readOnly: true 2107 | thumbnail: 2108 | type: string 2109 | description: URL to clip thumbnail 2110 | readOnly: true 2111 | required: 2112 | - status 2113 | - is_public 2114 | - title 2115 | FailedClip: 2116 | title: Failed Clip 2117 | type: object 2118 | allOf: 2119 | - $ref: '#/components/schemas/ClipProperties' 2120 | - type: object 2121 | properties: 2122 | status: 2123 | type: string 2124 | enum: 2125 | - failed 2126 | error_status: 2127 | description: Error message 2128 | type: string 2129 | enum: 2130 | - upload_failed_request 2131 | - upload_failed 2132 | - upload_failed_dcam 2133 | - upload_timed_out 2134 | - export_failed 2135 | readOnly: true 2136 | example: upload_failed 2137 | required: 2138 | - status 2139 | - is_public 2140 | - title 2141 | Clip: 2142 | description: Video clip created from part of a route 2143 | type: object 2144 | oneOf: 2145 | - $ref: '#/components/schemas/PendingClip' 2146 | - $ref: '#/components/schemas/DoneClip' 2147 | - $ref: '#/components/schemas/FailedClip' 2148 | JSONRPCPayload: 2149 | type: object 2150 | properties: 2151 | method: 2152 | type: string 2153 | description: JSON-RPC method name 2154 | jsonrpc: 2155 | type: string 2156 | description: JSON-RPC version 2157 | enum: 2158 | - "2.0" 2159 | id: 2160 | type: integer 2161 | description: JSON-RPC request ID 2162 | enum: 2163 | - 0 2164 | required: 2165 | - method 2166 | - jsonrpc 2167 | - id 2168 | JSONRPCResponse: 2169 | type: object 2170 | properties: 2171 | jsonrpc: 2172 | type: string 2173 | description: JSON-RPC version 2174 | enum: 2175 | - "2.0" 2176 | id: 2177 | type: integer 2178 | description: JSON-RPC request ID 2179 | enum: 2180 | - 0 2181 | result: 2182 | description: Method-specific result 2183 | required: 2184 | - jsonrpc 2185 | - id 2186 | GetMessageMethod: 2187 | type: object 2188 | allOf: 2189 | - $ref: '#/components/schemas/JSONRPCPayload' 2190 | - type: object 2191 | properties: 2192 | method: 2193 | type: string 2194 | enum: 2195 | - getMessage 2196 | params: 2197 | type: object 2198 | properties: 2199 | service: 2200 | type: string 2201 | description: service name 2202 | example: peripheralState 2203 | timeout: 2204 | type: integer 2205 | description: time to wait for a message in milliseconds 2206 | example: 5000 2207 | required: 2208 | - service 2209 | required: 2210 | - params 2211 | GetVersionMethod: 2212 | type: object 2213 | allOf: 2214 | - $ref: '#/components/schemas/JSONRPCPayload' 2215 | - type: object 2216 | properties: 2217 | method: 2218 | type: string 2219 | enum: 2220 | - getVersion 2221 | SetNavDestinationMethod: 2222 | type: object 2223 | allOf: 2224 | - $ref: '#/components/schemas/JSONRPCPayload' 2225 | - type: object 2226 | properties: 2227 | method: 2228 | type: string 2229 | enum: 2230 | - setNavDestination 2231 | params: 2232 | $ref: '#/components/schemas/NavigationDestination' 2233 | required: 2234 | - params 2235 | RebootMethod: 2236 | type: object 2237 | allOf: 2238 | - $ref: '#/components/schemas/JSONRPCPayload' 2239 | - type: object 2240 | properties: 2241 | method: 2242 | type: string 2243 | enum: 2244 | - reboot 2245 | UploadFilesToUrlsMethod: 2246 | type: object 2247 | allOf: 2248 | - $ref: '#/components/schemas/JSONRPCPayload' 2249 | - type: object 2250 | properties: 2251 | method: 2252 | type: string 2253 | enum: 2254 | - uploadFilesToUrls 2255 | params: 2256 | type: array 2257 | items: 2258 | type: object 2259 | properties: 2260 | fn: 2261 | type: string 2262 | url: 2263 | type: string 2264 | headers: 2265 | type: object 2266 | additionalProperties: 2267 | type: string 2268 | default: {} 2269 | example: 2270 | x-ms-blob-type: BlockBlob 2271 | allow_cellular: 2272 | type: boolean 2273 | default: false 2274 | required: 2275 | - fn 2276 | - url 2277 | expiry: 2278 | type: integer 2279 | description: | 2280 | Unix timestamp at which this message will be removed from the offline queue, after 2281 | which it will no longer be sent to the device if it comes online. If not specified, 2282 | this message will not be added to the offline queue. 2283 | required: 2284 | - params 2285 | ListUploadQueueMethod: 2286 | type: object 2287 | allOf: 2288 | - $ref: '#/components/schemas/JSONRPCPayload' 2289 | - type: object 2290 | properties: 2291 | method: 2292 | type: string 2293 | enum: 2294 | - listUploadQueue 2295 | GetPublicKeyMethod: 2296 | type: object 2297 | allOf: 2298 | - $ref: '#/components/schemas/JSONRPCPayload' 2299 | - type: object 2300 | properties: 2301 | method: 2302 | type: string 2303 | enum: 2304 | - getPublicKey 2305 | GetSshAuthorizedKeysMethod: 2306 | type: object 2307 | allOf: 2308 | - $ref: '#/components/schemas/JSONRPCPayload' 2309 | - type: object 2310 | properties: 2311 | method: 2312 | type: string 2313 | enum: 2314 | - getSshAuthorizedKeys 2315 | GetSimInfoMethod: 2316 | type: object 2317 | allOf: 2318 | - $ref: '#/components/schemas/JSONRPCPayload' 2319 | - type: object 2320 | properties: 2321 | method: 2322 | type: string 2323 | enum: 2324 | - getSimInfo 2325 | GetNetworkTypeMethod: 2326 | type: object 2327 | allOf: 2328 | - $ref: '#/components/schemas/JSONRPCPayload' 2329 | - type: object 2330 | properties: 2331 | method: 2332 | type: string 2333 | enum: 2334 | - getNetworkType 2335 | GetNetworkMeteredMethod: 2336 | type: object 2337 | allOf: 2338 | - $ref: '#/components/schemas/JSONRPCPayload' 2339 | - type: object 2340 | properties: 2341 | method: 2342 | type: string 2343 | enum: 2344 | - getNetworkMetered 2345 | GetNetworksMethod: 2346 | type: object 2347 | allOf: 2348 | - $ref: '#/components/schemas/JSONRPCPayload' 2349 | - type: object 2350 | properties: 2351 | method: 2352 | type: string 2353 | enum: 2354 | - getNetworks 2355 | TakeSnapshotMethod: 2356 | type: object 2357 | allOf: 2358 | - $ref: '#/components/schemas/JSONRPCPayload' 2359 | - type: object 2360 | properties: 2361 | method: 2362 | type: string 2363 | enum: 2364 | - takeSnapshot 2365 | AthenaPayload: 2366 | type: object 2367 | oneOf: 2368 | - $ref: '#/components/schemas/GetMessageMethod' 2369 | - $ref: '#/components/schemas/GetVersionMethod' 2370 | - $ref: '#/components/schemas/SetNavDestinationMethod' 2371 | - $ref: '#/components/schemas/RebootMethod' 2372 | - $ref: '#/components/schemas/UploadFilesToUrlsMethod' 2373 | - $ref: '#/components/schemas/ListUploadQueueMethod' 2374 | - $ref: '#/components/schemas/GetPublicKeyMethod' 2375 | - $ref: '#/components/schemas/GetSshAuthorizedKeysMethod' 2376 | - $ref: '#/components/schemas/GetSimInfoMethod' 2377 | - $ref: '#/components/schemas/GetNetworkTypeMethod' 2378 | - $ref: '#/components/schemas/GetNetworkMeteredMethod' 2379 | - $ref: '#/components/schemas/GetNetworksMethod' 2380 | - $ref: '#/components/schemas/TakeSnapshotMethod' 2381 | AthenaResponse: 2382 | type: object 2383 | allOf: 2384 | - $ref: '#/components/schemas/JSONRPCResponse' 2385 | example: 2386 | jsonrpc: "2.0" 2387 | id: 0 2388 | result: 2389 | success: 1 2390 | parameters: 2391 | dongleId: 2392 | name: dongleId 2393 | description: Dongle ID 2394 | in: path 2395 | required: true 2396 | schema: 2397 | $ref: '#/components/schemas/DongleID' 2398 | routeName: 2399 | name: routeName 2400 | description: Canonical route name 2401 | in: path 2402 | required: true 2403 | schema: 2404 | $ref: '#/components/schemas/RouteName' 2405 | --------------------------------------------------------------------------------