curl --request POST \
--url https://api.pid.peridotvault.com/v1/permissions/grants/validate \
--header 'Content-Type: application/json' \
--cookie pid_access= \
--data '
{
"kind": 3,
"target": "<string>",
"selector": "<string>",
"token": "<string>",
"to": "<string>",
"perTxCap": "<string>",
"totalLimit": "<string>",
"nftId": "<string>",
"sessionX": "<string>",
"sessionY": "<string>",
"validAfter": 123,
"validUntil": 123,
"salt": "<string>",
"deadline": 123,
"chainId": 123,
"account": "<string>",
"nonce": "<string>",
"factory": "<string>"
}
'const options = {
method: 'POST',
headers: {cookie: 'pid_access=', 'Content-Type': 'application/json'},
body: JSON.stringify({
kind: 3,
target: '<string>',
selector: '<string>',
token: '<string>',
to: '<string>',
perTxCap: '<string>',
totalLimit: '<string>',
nftId: '<string>',
sessionX: '<string>',
sessionY: '<string>',
validAfter: 123,
validUntil: 123,
salt: '<string>',
deadline: 123,
chainId: 123,
account: '<string>',
nonce: '<string>',
factory: '<string>'
})
};
fetch('https://api.pid.peridotvault.com/v1/permissions/grants/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pid.peridotvault.com/v1/permissions/grants/validate"
payload = {
"kind": 3,
"target": "<string>",
"selector": "<string>",
"token": "<string>",
"to": "<string>",
"perTxCap": "<string>",
"totalLimit": "<string>",
"nftId": "<string>",
"sessionX": "<string>",
"sessionY": "<string>",
"validAfter": 123,
"validUntil": 123,
"salt": "<string>",
"deadline": 123,
"chainId": 123,
"account": "<string>",
"nonce": "<string>",
"factory": "<string>"
}
headers = {
"cookie": "pid_access=",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pid.peridotvault.com/v1/permissions/grants/validate"
payload := strings.NewReader("{\n \"kind\": 3,\n \"target\": \"<string>\",\n \"selector\": \"<string>\",\n \"token\": \"<string>\",\n \"to\": \"<string>\",\n \"perTxCap\": \"<string>\",\n \"totalLimit\": \"<string>\",\n \"nftId\": \"<string>\",\n \"sessionX\": \"<string>\",\n \"sessionY\": \"<string>\",\n \"validAfter\": 123,\n \"validUntil\": 123,\n \"salt\": \"<string>\",\n \"deadline\": 123,\n \"chainId\": 123,\n \"account\": \"<string>\",\n \"nonce\": \"<string>\",\n \"factory\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("cookie", "pid_access=")
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))
}{
"permissionId": "<string>",
"grantPayload": "<string>"
}{
"statusCode": 400,
"message": [
"handle must be 3-20 chars: lowercase letters, numbers, underscore"
]
}{
"statusCode": 401,
"message": "Unauthorized"
}{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests"
}Validate a permission grant and return its canonical id + owner challenge
Pure scope-hygiene check mirroring grantPermission (kind rules, denylist,
30d TTL, caps) plus the canonical permissionId and the owner challenge
(what the passkey signs). Authenticated; submission-time pid→account
binding is enforced by the relay path.
curl --request POST \
--url https://api.pid.peridotvault.com/v1/permissions/grants/validate \
--header 'Content-Type: application/json' \
--cookie pid_access= \
--data '
{
"kind": 3,
"target": "<string>",
"selector": "<string>",
"token": "<string>",
"to": "<string>",
"perTxCap": "<string>",
"totalLimit": "<string>",
"nftId": "<string>",
"sessionX": "<string>",
"sessionY": "<string>",
"validAfter": 123,
"validUntil": 123,
"salt": "<string>",
"deadline": 123,
"chainId": 123,
"account": "<string>",
"nonce": "<string>",
"factory": "<string>"
}
'const options = {
method: 'POST',
headers: {cookie: 'pid_access=', 'Content-Type': 'application/json'},
body: JSON.stringify({
kind: 3,
target: '<string>',
selector: '<string>',
token: '<string>',
to: '<string>',
perTxCap: '<string>',
totalLimit: '<string>',
nftId: '<string>',
sessionX: '<string>',
sessionY: '<string>',
validAfter: 123,
validUntil: 123,
salt: '<string>',
deadline: 123,
chainId: 123,
account: '<string>',
nonce: '<string>',
factory: '<string>'
})
};
fetch('https://api.pid.peridotvault.com/v1/permissions/grants/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.pid.peridotvault.com/v1/permissions/grants/validate"
payload = {
"kind": 3,
"target": "<string>",
"selector": "<string>",
"token": "<string>",
"to": "<string>",
"perTxCap": "<string>",
"totalLimit": "<string>",
"nftId": "<string>",
"sessionX": "<string>",
"sessionY": "<string>",
"validAfter": 123,
"validUntil": 123,
"salt": "<string>",
"deadline": 123,
"chainId": 123,
"account": "<string>",
"nonce": "<string>",
"factory": "<string>"
}
headers = {
"cookie": "pid_access=",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.pid.peridotvault.com/v1/permissions/grants/validate"
payload := strings.NewReader("{\n \"kind\": 3,\n \"target\": \"<string>\",\n \"selector\": \"<string>\",\n \"token\": \"<string>\",\n \"to\": \"<string>\",\n \"perTxCap\": \"<string>\",\n \"totalLimit\": \"<string>\",\n \"nftId\": \"<string>\",\n \"sessionX\": \"<string>\",\n \"sessionY\": \"<string>\",\n \"validAfter\": 123,\n \"validUntil\": 123,\n \"salt\": \"<string>\",\n \"deadline\": 123,\n \"chainId\": 123,\n \"account\": \"<string>\",\n \"nonce\": \"<string>\",\n \"factory\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("cookie", "pid_access=")
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))
}{
"permissionId": "<string>",
"grantPayload": "<string>"
}{
"statusCode": 400,
"message": [
"handle must be 3-20 chars: lowercase letters, numbers, underscore"
]
}{
"statusCode": 401,
"message": "Unauthorized"
}{
"statusCode": 429,
"message": "ThrottlerException: Too Many Requests"
}Authorizations
JWT access token set by /auth/google/callback or /auth/refresh
Body
1 = nonfinancial, 2 = ETH, 3 = ERC-20, 4 = ERC-721, 5 = ERC-1155
1 <= x <= 5Nonfinancial: exact allowed target (never the account/factory)
Nonfinancial: exact allowed selector (0x + 8 hex chars, never denylisted)
Financial: token contract (zero address = native ETH)
Financial: explicit recipient
Financial: max per execution, raw units
Financial: lifetime cap in the same units
ERC-721/1155: token id
Session P-256 key x (0x + 64 hex chars)
Session P-256 key y (0x + 64 hex chars)
Grant start (unix seconds)
Grant expiry (unix seconds, ≤ 30d out)
Owner-chosen uniqueness salt (0x + 64 hex chars)
Owner-authorization deadline (unix seconds, TTL ≤ 600s)
Owner nonce the grant consumes
Deploying factory (blocked as a permission target)