curl --request POST \
--url https://api.airctrl.dev/v1/service-accounts/{id}/grants \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'x-account-id: <x-account-id>' \
--data '
{
"recordId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"wrappedDek": {},
"saIdentityPublicKeyB64": "<string>",
"dekVersion": 2,
"sourceWrappedDek": {},
"sourceProjectKeyVersion": 2
}
'import requests
url = "https://api.airctrl.dev/v1/service-accounts/{id}/grants"
payload = {
"recordId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"wrappedDek": {},
"saIdentityPublicKeyB64": "<string>",
"dekVersion": 2,
"sourceWrappedDek": {},
"sourceProjectKeyVersion": 2
}
headers = {
"x-account-id": "<x-account-id>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-account-id': '<x-account-id>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recordId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
wrappedDek: {},
saIdentityPublicKeyB64: '<string>',
dekVersion: 2,
sourceWrappedDek: {},
sourceProjectKeyVersion: 2
})
};
fetch('https://api.airctrl.dev/v1/service-accounts/{id}/grants', 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.airctrl.dev/v1/service-accounts/{id}/grants",
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([
'recordId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'wrappedDek' => [
],
'saIdentityPublicKeyB64' => '<string>',
'dekVersion' => 2,
'sourceWrappedDek' => [
],
'sourceProjectKeyVersion' => 2
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"x-account-id: <x-account-id>"
],
]);
$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.airctrl.dev/v1/service-accounts/{id}/grants"
payload := strings.NewReader("{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-account-id", "<x-account-id>")
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.airctrl.dev/v1/service-accounts/{id}/grants")
.header("x-account-id", "<x-account-id>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.airctrl.dev/v1/service-accounts/{id}/grants")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-account-id"] = '<x-account-id>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"granted": true
}
}{
"ok": false,
"error": {
"code": "bad_request",
"message": "Request validation failed",
"details": [
"Check the submitted values."
]
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "unauthorized",
"message": "Authentication is required"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "forbidden",
"message": "not_authorized"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "conflict",
"message": "idempotency_key_reused_with_different_params"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Too many requests"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "internal_error",
"message": "Internal server error"
},
"requestId": "req_example"
}Grant a record to a Service Account
Stores a record key encrypted for the current Service Account identity. Requires service-accounts:grant-record. Credential lifecycle remains Web-only.
curl --request POST \
--url https://api.airctrl.dev/v1/service-accounts/{id}/grants \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'x-account-id: <x-account-id>' \
--data '
{
"recordId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"wrappedDek": {},
"saIdentityPublicKeyB64": "<string>",
"dekVersion": 2,
"sourceWrappedDek": {},
"sourceProjectKeyVersion": 2
}
'import requests
url = "https://api.airctrl.dev/v1/service-accounts/{id}/grants"
payload = {
"recordId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"wrappedDek": {},
"saIdentityPublicKeyB64": "<string>",
"dekVersion": 2,
"sourceWrappedDek": {},
"sourceProjectKeyVersion": 2
}
headers = {
"x-account-id": "<x-account-id>",
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'x-account-id': '<x-account-id>',
Authorization: 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
recordId: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
wrappedDek: {},
saIdentityPublicKeyB64: '<string>',
dekVersion: 2,
sourceWrappedDek: {},
sourceProjectKeyVersion: 2
})
};
fetch('https://api.airctrl.dev/v1/service-accounts/{id}/grants', 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.airctrl.dev/v1/service-accounts/{id}/grants",
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([
'recordId' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'wrappedDek' => [
],
'saIdentityPublicKeyB64' => '<string>',
'dekVersion' => 2,
'sourceWrappedDek' => [
],
'sourceProjectKeyVersion' => 2
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"x-account-id: <x-account-id>"
],
]);
$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.airctrl.dev/v1/service-accounts/{id}/grants"
payload := strings.NewReader("{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-account-id", "<x-account-id>")
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.airctrl.dev/v1/service-accounts/{id}/grants")
.header("x-account-id", "<x-account-id>")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.airctrl.dev/v1/service-accounts/{id}/grants")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-account-id"] = '<x-account-id>'
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"recordId\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"wrappedDek\": {},\n \"saIdentityPublicKeyB64\": \"<string>\",\n \"dekVersion\": 2,\n \"sourceWrappedDek\": {},\n \"sourceProjectKeyVersion\": 2\n}"
response = http.request(request)
puts response.read_body{
"ok": true,
"data": {
"granted": true
}
}{
"ok": false,
"error": {
"code": "bad_request",
"message": "Request validation failed",
"details": [
"Check the submitted values."
]
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "unauthorized",
"message": "Authentication is required"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "forbidden",
"message": "not_authorized"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "conflict",
"message": "idempotency_key_reused_with_different_params"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "rate_limited",
"message": "Too many requests"
},
"requestId": "req_example"
}{
"ok": false,
"error": {
"code": "internal_error",
"message": "Internal server error"
},
"requestId": "req_example"
}Authorizations
Human personal access token (sk-actrl-pat-...).
Headers
Account scope used for permission resolution. It must own the target resource.
Stable key for safely retrying this write. Reuse the same key only with the same request body; results are retained for 24 hours and conflicting reuse returns 409.
Path Parameters
Body
Record shared with the Service Account.
Record key encrypted for the Service Account.
Service Account public key used to encrypt the record key.
Record DEK version read before opening its source wrapper.
x >= 1Exact encrypted source wrapper opened by the client; never plaintext key material.
Required only when the source is the project copy; omit for an individual wrapper.
x >= 1