curl --request POST \
--url https://api.canary.effectiveai.app/api/v2/device-authorizations \
--header 'Content-Type: application/json' \
--data '
{
"clientName": "Claude Code",
"workspace": "acme"
}
'import requests
url = "https://api.canary.effectiveai.app/api/v2/device-authorizations"
payload = {
"clientName": "Claude Code",
"workspace": "acme"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({clientName: 'Claude Code', workspace: 'acme'})
};
fetch('https://api.canary.effectiveai.app/api/v2/device-authorizations', 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.canary.effectiveai.app/api/v2/device-authorizations",
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([
'clientName' => 'Claude Code',
'workspace' => 'acme'
]),
CURLOPT_HTTPHEADER => [
"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.canary.effectiveai.app/api/v2/device-authorizations"
payload := strings.NewReader("{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.canary.effectiveai.app/api/v2/device-authorizations")
.header("Content-Type", "application/json")
.body("{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.canary.effectiveai.app/api/v2/device-authorizations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}"
response = http.request(request)
puts response.read_body{
"deviceCode": "<string>",
"userCode": "BCDF-GHJK",
"verificationUri": "https://acme.effectiveai.app/device",
"verificationUriComplete": "https://acme.effectiveai.app/device?code=BCDF-GHJK",
"expiresAt": "2023-11-07T05:31:56Z",
"expiresIn": 600,
"interval": 5
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}Start a browser sign-in for an agent
Start the device sign-in that gives an agent an API key without the user copying one. No credentials are needed. Show the user verificationUriComplete and userCode, then call exchangeDeviceAuthorization with deviceCode every interval seconds until the user approves. A signed-in user of the workspace approves the request in the browser. Requests expire after expiresIn seconds. Each call starts a new request; nothing is created for the user until they approve.
curl --request POST \
--url https://api.canary.effectiveai.app/api/v2/device-authorizations \
--header 'Content-Type: application/json' \
--data '
{
"clientName": "Claude Code",
"workspace": "acme"
}
'import requests
url = "https://api.canary.effectiveai.app/api/v2/device-authorizations"
payload = {
"clientName": "Claude Code",
"workspace": "acme"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({clientName: 'Claude Code', workspace: 'acme'})
};
fetch('https://api.canary.effectiveai.app/api/v2/device-authorizations', 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.canary.effectiveai.app/api/v2/device-authorizations",
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([
'clientName' => 'Claude Code',
'workspace' => 'acme'
]),
CURLOPT_HTTPHEADER => [
"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.canary.effectiveai.app/api/v2/device-authorizations"
payload := strings.NewReader("{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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.canary.effectiveai.app/api/v2/device-authorizations")
.header("Content-Type", "application/json")
.body("{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.canary.effectiveai.app/api/v2/device-authorizations")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"clientName\": \"Claude Code\",\n \"workspace\": \"acme\"\n}"
response = http.request(request)
puts response.read_body{
"deviceCode": "<string>",
"userCode": "BCDF-GHJK",
"verificationUri": "https://acme.effectiveai.app/device",
"verificationUriComplete": "https://acme.effectiveai.app/device?code=BCDF-GHJK",
"expiresAt": "2023-11-07T05:31:56Z",
"expiresIn": 600,
"interval": 5
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}{
"error": "<string>",
"code": "<string>",
"details": [
{
"location": "path",
"path": [
"<string>"
],
"code": "required",
"message": "<string>"
}
],
"requestId": "<string>",
"dataset": "naic",
"accessUrl": "<string>"
}Body
What is asking for access, shown to the user on the approval page and used as the API key name, such as "Claude Code".
1 - 80^[^\p{Cc}"\\]+$/u"Claude Code"
The workspace to sign in to: the first part of the address the user opens Effective at, such as acme for acme.effectiveai.app. Only a user of this workspace can approve the request.
^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"acme"
Response
Sign-in started. Show the link and code to the user.
Secret for exchangeDeviceAuthorization. Keep it private and never show it to the user.
Short code the user confirms on the approval page.
"BCDF-GHJK"
Approval page, where the user can type the code.
"https://acme.effectiveai.app/device"
Approval page with the code filled in. Show this link to the user.
"https://acme.effectiveai.app/device?code=BCDF-GHJK"
When the request expires if nobody approves it.
Seconds until expiresAt.
x > 0600
Minimum seconds between exchangeDeviceAuthorization calls.
x > 05