curl --request POST \
--url https://api.flow.engineer/v1/device/authorizations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"claim_token": "<string>",
"client_name": "<string>"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({claim_token: '<string>', client_name: '<string>'})
};
fetch('https://api.flow.engineer/v1/device/authorizations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/device/authorizations"
payload = {
"claim_token": "<string>",
"client_name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"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.flow.engineer/v1/device/authorizations"
payload := strings.NewReader("{\n \"claim_token\": \"<string>\",\n \"client_name\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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))
}{
"device_code": "fdc_...",
"user_code": "WDJB-MJHT",
"verification_uri": "https://api.flow.engineer/admin/device",
"verification_uri_complete": "https://api.flow.engineer/admin/device?code=WDJB-MJHT",
"expires_in": 1,
"expires_at": "2023-11-07T05:31:56Z",
"interval": 2
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Start a sign-in from an agent or CLI (device flow)
Starts a sign-in that a person finishes in a browser with GitHub
(the OAuth 2.0 device authorization grant, RFC 8628, in Flow’s JSON shape).
Show the person verification_uri and user_code: they open the page,
sign in and type the code you show them. Then poll POST /v1/device/token
with device_code every interval seconds until it returns a key.
To claim an app made with POST /v1/sandbox/keys, pass its claim_token,
or send that app’s test key as Authorization: Bearer fk_test_... (a key
of an app that is already claimed is ignored). When
the person approves, the app joins their account: its data and keys are
kept, its keys no longer expire (a key that expired less than 30 days ago
works again), and the app moves under the person’s signed-in allowance: 3
contacts and 100 messages each, one allowance per person, shared by every
app they own or claim. A person may claim up to 10 apps; past that the
approval page refuses the claim. An expired key claims its app only for 30
days after its expires_at; after that, use the claim_token. Without
either, approving gives a new test key for the person’s own app (made at
their first sign-in).
The approval page asks the person to type user_code as the agent or CLI
shows it, so a link alone cannot approve a sign-in someone else started.
No API key is needed. Calls are limited per client address and network.
curl --request POST \
--url https://api.flow.engineer/v1/device/authorizations \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"claim_token": "<string>",
"client_name": "<string>"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({claim_token: '<string>', client_name: '<string>'})
};
fetch('https://api.flow.engineer/v1/device/authorizations', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/device/authorizations"
payload = {
"claim_token": "<string>",
"client_name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"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.flow.engineer/v1/device/authorizations"
payload := strings.NewReader("{\n \"claim_token\": \"<string>\",\n \"client_name\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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))
}{
"device_code": "fdc_...",
"user_code": "WDJB-MJHT",
"verification_uri": "https://api.flow.engineer/admin/device",
"verification_uri_complete": "https://api.flow.engineer/admin/device?code=WDJB-MJHT",
"expires_in": 1,
"expires_at": "2023-11-07T05:31:56Z",
"interval": 2
}{
"error": {
"type": "invalid_request",
"message": "limit must be between 1 and 100.",
"hint": "Pass limit between 1 and 100 (default 20), and page with after or before.",
"doc_url": "https://api.flow.engineer/docs/errors/invalid_request",
"param": "limit"
}
}{
"error": {
"type": "authentication",
"message": "No valid API key was given.",
"hint": "Send the header Authorization: Bearer fk_test_... (or fk_live_...); no key yet? Get a test key with curl -X POST https://api.flow.engineer/v1/sandbox/keys",
"doc_url": "https://api.flow.engineer/docs/errors/authentication"
}
}{
"error": {
"type": "new_contact_limit",
"message": "This sender has started its 15 new conversations for today.",
"hint": "Retry after 3600 seconds; replies into existing conversations still go.",
"doc_url": "https://api.flow.engineer/docs/errors/new_contact_limit",
"retry_after": 3600,
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T"
}
}{
"error": {
"type": "invalid_request",
"message": "<string>",
"hint": "Send a template instead: POST /v1/messages with content.type=template.",
"doc_url": "https://api.flow.engineer/docs/errors/outside_window",
"param": "<string>",
"retry_after": 1,
"conversation": "conv_01JB8ZC3K5M7P9R1T3V5X7Z9B1",
"sender": "snd_01JB8Z4Q3V6W0R2N7C5H1M9K4T",
"channel_code": "<string>",
"request_id": "<string>"
}
}Authorizations
An API key of one app, sent as Authorization: Bearer <key>. Keys start with
fk_test_ (test mode: sandbox senders and test data only) or fk_live_
(live mode). Keep live keys on your server; never ship them in an app or page.
Body
Response
The sign-in was started. Show the person the link and code, then poll for the key.
A device sign-in waiting for a person to approve it in a browser.
The secret you poll POST /v1/device/token with. Never show it to the person.
"fdc_..."
The code the person types on the approval page, eight letters in two groups. Always show it to the person, also when you show verification_uri_complete.
"WDJB-MJHT"
The page where the person signs in and enters user_code.
"https://api.flow.engineer/admin/device"
The same page for this sign-in. Show this link (or a QR code of it) to the person together with user_code; the page asks them to type the code they see from you and checks it against the link, so a link on its own cannot approve a sign-in.
"https://api.flow.engineer/admin/device?code=WDJB-MJHT"
Seconds until the codes expire (15 minutes).
x >= 0When the codes expire.
Seconds to wait between polls of POST /v1/device/token.
x >= 1