curl --request POST \
--url https://api.mixpeek.com/v1/auth/token-exchange \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"provider_id": "wip_abc123",
"subject_token": "<the workload's OIDC token>"
}
EOFimport requests
url = "https://api.mixpeek.com/v1/auth/token-exchange"
payload = {
"provider_id": "wip_abc123",
"subject_token": "<the workload's OIDC token>"
}
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({provider_id: 'wip_abc123', subject_token: '<the workload\'s OIDC token>'})
};
fetch('https://api.mixpeek.com/v1/auth/token-exchange', 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.mixpeek.com/v1/auth/token-exchange",
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([
'provider_id' => 'wip_abc123',
'subject_token' => '<the workload\'s OIDC token>'
]),
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.mixpeek.com/v1/auth/token-exchange"
payload := strings.NewReader("{\n \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\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.mixpeek.com/v1/auth/token-exchange")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.mixpeek.com/v1/auth/token-exchange")
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 \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\n}"
response = http.request(request)
puts response.read_body{
"access_token": "<string>",
"expires_in": 123,
"expires_at": "2023-11-07T05:31:56Z",
"key_id": "<string>",
"organization_id": "<string>",
"permissions": [
"read"
],
"token_type": "Bearer"
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}Exchange a workload OIDC token for a short-lived API key
Trade the OIDC token a workload’s platform issued for a Mixpeek API key.
Configure a workload identity provider on the organization first (PATCH /v1/organizations with workload_identity_providers, or Studio >
Settings > Organization > Workload identity). The workload then sends its
token here with the provider’s provider_id and uses the returned
access_token as Authorization: Bearer <access_token> until
expires_at. No API key is needed to call this route.
On Vercel, the token is getVercelOidcToken() from @vercel/oidc; on
GitHub Actions it is core.getIDToken(audience) with permissions: id-token: write.
curl --request POST \
--url https://api.mixpeek.com/v1/auth/token-exchange \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data @- <<EOF
{
"provider_id": "wip_abc123",
"subject_token": "<the workload's OIDC token>"
}
EOFimport requests
url = "https://api.mixpeek.com/v1/auth/token-exchange"
payload = {
"provider_id": "wip_abc123",
"subject_token": "<the workload's OIDC token>"
}
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({provider_id: 'wip_abc123', subject_token: '<the workload\'s OIDC token>'})
};
fetch('https://api.mixpeek.com/v1/auth/token-exchange', 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.mixpeek.com/v1/auth/token-exchange",
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([
'provider_id' => 'wip_abc123',
'subject_token' => '<the workload\'s OIDC token>'
]),
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.mixpeek.com/v1/auth/token-exchange"
payload := strings.NewReader("{\n \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\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.mixpeek.com/v1/auth/token-exchange")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.mixpeek.com/v1/auth/token-exchange")
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 \"provider_id\": \"wip_abc123\",\n \"subject_token\": \"<the workload's OIDC token>\"\n}"
response = http.request(request)
puts response.read_body{
"access_token": "<string>",
"expires_in": 123,
"expires_at": "2023-11-07T05:31:56Z",
"key_id": "<string>",
"organization_id": "<string>",
"permissions": [
"read"
],
"token_type": "Bearer"
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}{
"error": {
"details": {
"id": "ns_123",
"resource": "namespace"
},
"message": "Namespace not found",
"type": "NotFoundError"
},
"status": 404,
"success": false
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
Keys this body does not declare are refused with 422 extra_forbidden naming the accepted keys (since 2026-11-02).
Exchange a workload's OIDC token for a short-lived Mixpeek API key.
The workload identity provider configured on the organization.
1The OIDC token the workload's platform issued: on Vercel, getVercelOidcToken() or the x-vercel-oidc-token header; on GitHub Actions, core.getIDToken(audience).
1Requested key lifetime. Capped at the provider's token_ttl_seconds and at the subject token's own expiry plus the provider lifetime.
60 <= x <= 3600Response
Successful Response
A short-lived API key. Send it as Authorization: Bearer <access_token>.
The API key. Shown once.
Seconds until the key expires.
When the key expires (UTC).
Identifier of the minted key, for audit.
Organization the key belongs to.
Permissions of the key.
Simplified API key permissions.
This four-value enum replaces the legacy 16-permission model. Keep usage simple: prefer the least privileged option that satisfies the workflow.
Hierarchy (strongest -> weakest): ADMIN > DELETE > WRITE > READ.
read, write, delete, admin "Bearer"Was this page helpful?

