Delete Pat
curl --request DELETE \
--url https://api.example.com/organization-pats/{id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.example.com/organization-pats/{id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.delete(url, headers=headers)
print(response.text)const options = {method: 'DELETE', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.example.com/organization-pats/{id}', 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.example.com/organization-pats/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/organization-pats/{id}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.example.com/organization-pats/{id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/organization-pats/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"message": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}PATs
Delete an Organization API Key
DELETE
/
organization-pats
/
{id}
Delete Pat
curl --request DELETE \
--url https://api.example.com/organization-pats/{id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.example.com/organization-pats/{id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.delete(url, headers=headers)
print(response.text)const options = {method: 'DELETE', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.example.com/organization-pats/{id}', 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.example.com/organization-pats/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/organization-pats/{id}"
req, _ := http.NewRequest("DELETE", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.delete("https://api.example.com/organization-pats/{id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/organization-pats/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"message": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}Deleting the local
pats row is what actually revokes the key: the exchange looks the key up there on every request, so a deleted row means an immediate 401 for anything still using it.
Row-level security scopes the lookup to the caller’s active organization, so an id from a different org behaves exactly like an unknown one — 404, never a 403 that would confirm the row exists elsewhere. The paired Logto-side personal access token is deleted first, addressed by the key’s owner rather than the caller, so an admin revoking someone else’s key cleans up the right upstream token instead of orphaning it. A Logto-side 404 — already gone, nothing left to orphan — is tolerated and logged, and the local row is still deleted.
Auth
Requirespats:delete and an active organization.
By default you can only delete your own keys: the lookup is filtered on the caller’s user_id, so another member’s id returns the same 404 as an unknown one and gives you no way to enumerate keys you don’t own. Row-level security alone does not cover this — it enforces only the organization boundary.
A caller holding organizations:manage (the admin role) may delete any key in the organization, so a departed member’s integration key stays revokable. Admin revocations of someone else’s key are logged with both user ids.
Response
200
{ "message": "PAT deleted successfully" }
Errors
| Status | Cause |
|---|---|
404 Not Found | No key with this id in the caller’s organization — or, without organizations:manage, a key in it that belongs to another member. |
| Logto’s own status, verbatim | Deleting the paired token from Logto failed for a reason other than “already gone.” Logto’s status code is re-raised as-is with a generic Failed to delete token from identity provider. detail — so a Logto 403 surfaces to you as a 403, which is easy to misread as your token lacking permission. |
500 Internal Server Error | Logto was unreachable (transport-level failure — connection refused, DNS, timeout). |