curl --request POST \
--url https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"grace_hours": 24
}
'import requests
url = "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate"
payload = { "grace_hours": 24 }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({grace_hours: 24})
};
fetch('https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'grace_hours' => 24
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate"
payload := strings.NewReader("{\n \"grace_hours\": 24\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"grace_hours\": 24\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"grace_hours\": 24\n}"
response = http.request(request)
puts response.read_body{
"is_staging": false,
"id": "key_9TbR3xLm",
"object": "api_key",
"name": "Production — vehicle worker",
"prefix": "chk_live_a1b2",
"livemode": true,
"scopes": [
"<string>"
],
"created": 1755300000,
"last_used_at": 1755300000,
"revoked_at": 1755300000,
"secret": "chk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"created_by": "usr_5KpL2wQx",
"expires_at": 1755300000,
"rotates_at": 1755300000
}Rotate an API key
Issues a new secret for the same logical key and schedules the old one for expiry after
grace_hours (0 = immediate, default 24, maximum 72), so a fleet can be redeployed without a gap.
The new secret is shown once and preserves the source key’s permissions.
An already rotated source returns 409 key_already_rotated; rotate its successor instead.
Minimum role: admin.
curl --request POST \
--url https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"grace_hours": 24
}
'import requests
url = "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate"
payload = { "grace_hours": 24 }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({grace_hours: 24})
};
fetch('https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'grace_hours' => 24
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate"
payload := strings.NewReader("{\n \"grace_hours\": 24\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"grace_hours\": 24\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.trycarhub.com/mgmt/v1/api-keys/{id}/rotate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"grace_hours\": 24\n}"
response = http.request(request)
puts response.read_body{
"is_staging": false,
"id": "key_9TbR3xLm",
"object": "api_key",
"name": "Production — vehicle worker",
"prefix": "chk_live_a1b2",
"livemode": true,
"scopes": [
"<string>"
],
"created": 1755300000,
"last_used_at": 1755300000,
"revoked_at": 1755300000,
"secret": "chk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"created_by": "usr_5KpL2wQx",
"expires_at": 1755300000,
"rotates_at": 1755300000
}Authorizations
Short-lived JWT issued by the CarHub auth service (Better Auth), verified against its
JWKS. Claims: sub (user), org_id (active organisation) and role
(owner | admin | member | billing_manager). Used by the dashboard for /mgmt/v1/… only — it is never
accepted on /v1/….
Path Parameters
API key identifier (not the secret).
^key_[1-9A-HJ-NP-Za-km-z]{8,32}$Body
How many hours the previous secret keeps working. 0 expires it immediately.
0 <= x <= 72Response
Key rotated. Store the new secret now.
An organisation API key. The secret is never returned after creation.
Staging classification. Requires live mode and uses the organisation's shared live credits.
"key_9TbR3xLm"
"api_key""Production — vehicle worker"
Readable head of the key, enough to identify it in your logs.
"chk_live_a1b2"
Normalized scopes. Empty grants no business access. Wildcard grants no SDK permission. SDK write implies SDK read; storage write implies storage read.
Epoch seconds, UTC.
1755300000
Epoch seconds, UTC.
1755300000
Epoch seconds, UTC.
1755300000
The full key. Shown once, stored hashed, unrecoverable afterwards — put it in your secret manager before closing the response.
"chk_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
User who created the key, kept for audit. Revoking that user does not affect the key.
"usr_5KpL2wQx"
Scheduled expiry, when the key was minted with one. Null for a perpetual key.
1755300000
End of the grace period of a rotated key: after this instant the previous secret stops working. Null when the key was never rotated.
1755300000