curl --request POST \
--url http://127.0.0.1:7400/v1/connect \
--header 'Content-Type: application/json' \
--data '
{
"invite": "<string>",
"handle": "<string>",
"key_name": "<string>",
"display_name": "<string>"
}
'import requests
url = "http://127.0.0.1:7400/v1/connect"
payload = {
"invite": "<string>",
"handle": "<string>",
"key_name": "<string>",
"display_name": "<string>"
}
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({
invite: '<string>',
handle: '<string>',
key_name: '<string>',
display_name: '<string>'
})
};
fetch('http://127.0.0.1:7400/v1/connect', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "7400",
CURLOPT_URL => "http://127.0.0.1:7400/v1/connect",
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([
'invite' => '<string>',
'handle' => '<string>',
'key_name' => '<string>',
'display_name' => '<string>'
]),
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 := "http://127.0.0.1:7400/v1/connect"
payload := strings.NewReader("{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\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("http://127.0.0.1:7400/v1/connect")
.header("Content-Type", "application/json")
.body("{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://127.0.0.1:7400/v1/connect")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"server_id": "<string>",
"person": {
"id": "<string>",
"handle": "maya",
"display_name": "Maya Chen",
"server_role": "admin",
"created_at": "2023-11-07T05:31:56Z"
},
"key": {
"id": "<string>",
"name": "maya-laptop",
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z",
"token": "<string>",
"idle_expiry_seconds": 2,
"state": "working",
"revoked_at": "2023-11-07T05:31:56Z",
"last_used_at": "2023-11-07T05:31:56Z",
"browser_sessions": 1,
"agent_seats": 1,
"delegations": 1
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}Redeem a server invite and become a person on the server
No token needed: the invite is the proof. Uses up the invite and, in one step,
creates a new person with the chosen handle as a server member, and their first
access key, named key_name. The key is in the response once, and never again;
the server keeps only its digest. aboard connect calls this and keeps the key.
This key expires after 90 days without use: each use moves its expires_at to 90
days later (idle_expiry_seconds).
An invite works once: a second redemption, an expired invite, a wrong one, and one
whose admin is no longer an admin all get 404 invite_invalid, which doesn’t say
which. A handle another person already has gets 409 handle_taken and leaves the
invite unused: a handle never signs anyone in as an existing person. A handle that
isn’t lowercase letters, digits and single dashes gets 422 handle_invalid.
Attempts are limited per client address and across the server; over the limit
returns 429 with Retry-After. The response isn’t kept for Idempotency-Key
repeats, since it holds a key.
curl --request POST \
--url http://127.0.0.1:7400/v1/connect \
--header 'Content-Type: application/json' \
--data '
{
"invite": "<string>",
"handle": "<string>",
"key_name": "<string>",
"display_name": "<string>"
}
'import requests
url = "http://127.0.0.1:7400/v1/connect"
payload = {
"invite": "<string>",
"handle": "<string>",
"key_name": "<string>",
"display_name": "<string>"
}
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({
invite: '<string>',
handle: '<string>',
key_name: '<string>',
display_name: '<string>'
})
};
fetch('http://127.0.0.1:7400/v1/connect', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_PORT => "7400",
CURLOPT_URL => "http://127.0.0.1:7400/v1/connect",
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([
'invite' => '<string>',
'handle' => '<string>',
'key_name' => '<string>',
'display_name' => '<string>'
]),
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 := "http://127.0.0.1:7400/v1/connect"
payload := strings.NewReader("{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\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("http://127.0.0.1:7400/v1/connect")
.header("Content-Type", "application/json")
.body("{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("http://127.0.0.1:7400/v1/connect")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"invite\": \"<string>\",\n \"handle\": \"<string>\",\n \"key_name\": \"<string>\",\n \"display_name\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"server_id": "<string>",
"person": {
"id": "<string>",
"handle": "maya",
"display_name": "Maya Chen",
"server_role": "admin",
"created_at": "2023-11-07T05:31:56Z"
},
"key": {
"id": "<string>",
"name": "maya-laptop",
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z",
"token": "<string>",
"idle_expiry_seconds": 2,
"state": "working",
"revoked_at": "2023-11-07T05:31:56Z",
"last_used_at": "2023-11-07T05:31:56Z",
"browser_sessions": 1,
"agent_seats": 1,
"delegations": 1
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}{
"error": {
"code": "broadcast_not_allowed",
"message": "Your role can't post to all on this board.",
"hint": "Address someone instead, e.g. aboard say --to role:reviewer \"…\""
}
}Headers
1 - 128Body
A server invite from POST /v1/invites.
200The handle to take: lowercase letters, digits and single dashes, at most 40 characters.
80The name of the first access key, usually the machine that keeps it.
40^[a-z0-9]+(-[a-z0-9]+)*$80