curl --request POST \
--url https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"overlap_seconds": 3600,
"expires_in_days": 30,
"proof": {
"challenge_id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33",
"signature": "0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b"
}
}
'import requests
url = "https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations"
payload = {
"overlap_seconds": 3600,
"expires_in_days": 30,
"proof": {
"challenge_id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33",
"signature": "0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b"
}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
overlap_seconds: 3600,
expires_in_days: 30,
proof: {
challenge_id: '0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33',
signature: '0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b'
}
})
};
fetch('https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations', 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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations",
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([
'overlap_seconds' => 3600,
'expires_in_days' => 30,
'proof' => [
'challenge_id' => '0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33',
'signature' => '0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations"
payload := strings.NewReader("{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}"
response = http.request(request)
puts response.read_body{
"key": {
"id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a35",
"account": {
"kind": "PARTICIPANT",
"id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a30"
},
"environment": "production",
"label": "Trading bot",
"scopes": [
"account:read",
"account:stream",
"quote:accept",
"rfq:read",
"rfq:write"
],
"status": "ACTIVE",
"created_at": "2026-10-20T09:00:00Z",
"expires_at": "2026-11-19T09:00:00Z",
"last_used_at": null,
"revoked_at": null,
"rotated_from": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a31",
"overlap_until": null
},
"secret": "ttk_v1_prd_0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a35.q7Xv2LmP9sKd4RtYwB8nZc1HfGj6UeAo3VpNiMxTl0Q"
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}Rotate API key
Issues a replacement with the same label and scopes from a freshly signed ROTATE challenge, while the old key keeps working until its overlap_until.
Requires a wallet session or the Totalis app’s own sign-in session, sent with exactly one allowed Origin (otherwise FORBIDDEN); an API key cannot call it (UNAUTHENTICATED).
curl --request POST \
--url https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: <idempotency-key>' \
--data '
{
"overlap_seconds": 3600,
"expires_in_days": 30,
"proof": {
"challenge_id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33",
"signature": "0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b"
}
}
'import requests
url = "https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations"
payload = {
"overlap_seconds": 3600,
"expires_in_days": 30,
"proof": {
"challenge_id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33",
"signature": "0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b"
}
}
headers = {
"Idempotency-Key": "<idempotency-key>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'Idempotency-Key': '<idempotency-key>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
overlap_seconds: 3600,
expires_in_days: 30,
proof: {
challenge_id: '0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33',
signature: '0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b'
}
})
};
fetch('https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations', 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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations",
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([
'overlap_seconds' => 3600,
'expires_in_days' => 30,
'proof' => [
'challenge_id' => '0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33',
'signature' => '0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Idempotency-Key: <idempotency-key>"
],
]);
$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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations"
payload := strings.NewReader("{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Idempotency-Key", "<idempotency-key>")
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://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations")
.header("Idempotency-Key", "<idempotency-key>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://hip4-api.totalis.trade/v1/me/api-keys/{key_id}/rotations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Idempotency-Key"] = '<idempotency-key>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"overlap_seconds\": 3600,\n \"expires_in_days\": 30,\n \"proof\": {\n \"challenge_id\": \"0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a33\",\n \"signature\": \"0x4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a1b\"\n }\n}"
response = http.request(request)
puts response.read_body{
"key": {
"id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a35",
"account": {
"kind": "PARTICIPANT",
"id": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a30"
},
"environment": "production",
"label": "Trading bot",
"scopes": [
"account:read",
"account:stream",
"quote:accept",
"rfq:read",
"rfq:write"
],
"status": "ACTIVE",
"created_at": "2026-10-20T09:00:00Z",
"expires_at": "2026-11-19T09:00:00Z",
"last_used_at": null,
"revoked_at": null,
"rotated_from": "0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a31",
"overlap_until": null
},
"secret": "ttk_v1_prd_0199a1b2-c3d4-7e5f-8a6b-7c8d9e0f1a35.q7Xv2LmP9sKd4RtYwB8nZc1HfGj6UeAo3VpNiMxTl0Q"
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}{
"error": {
"code": "INVALID_REQUEST",
"field_violations": [
{
"code": "REQUIRED",
"field": "<string>"
}
],
"message": "<string>",
"request_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"retry": "NEVER"
}
}Authorizations
Wallet session: the access_token from POST /v1/auth/wallet-sessions, for a user who brings their own wallet to a partner's web page. It acts for that wallet's account with no scope limit, and is bound to the Origin it signed in from: send exactly one Origin header with that value, or the request is ORIGIN_REQUIRED or UNAUTHENTICATED. It lasts at most 30 days and ends after 7 idle days or at DELETE /v1/auth/wallet-sessions/current.
Headers
Lowercase UUIDv7 naming this command, unique per caller and route. A retry with the same key and body returns the original result; the same key with a different body returns 409 IDEMPOTENCY_KEY_REUSED.
^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$Path Parameters
ID of a key on this account, from the key list or the create response.
^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$Body
Every rotation needs a fresh owner proof over exactly this request without proof. The replacement inherits the predecessor's account, label and scopes.
The account owner's signature over the challenge for exactly this request without proof.
Show child attributes
Show child attributes
How long the old key keeps working after the rotation, 0 to 86400 seconds; 3600 when omitted.
0 <= x <= 86400The replacement's lifetime in days, 1 to 90, or 0 for no expiry; 30 when omitted.
0 <= x <= 90Response
The replacement key and its secret, shown only in this response.
The new key's metadata.
Show child attributes
Show child attributes
The ttk_ secret, shown only in this response; a retry of the same request answers SECRET_UNAVAILABLE, so store it now.
^ttk_v1_(dev|stg|prd)_[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[A-Za-z0-9_-]{43}$Was this page helpful?

