curl --request POST \
--url https://api.flow.engineer/v1/device/token \
--header 'Content-Type: application/json' \
--data '
{
"device_code": "<string>"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({device_code: '<string>'})
};
fetch('https://api.flow.engineer/v1/device/token', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/device/token"
payload = { "device_code": "<string>" }
headers = {"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/token"
payload := strings.NewReader("{\n \"device_code\": \"<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))
}{
"status": "pending",
"interval": 2,
"key": "<string>",
"api_key": {
"id": "key_01JB8Z1B2D4F6H8K0M2P4R6T8W",
"mode": "test",
"last4": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z"
},
"account": {
"id": "acct_01JB8YZ3N2Q4R6T8V0X2Z4B6D8",
"name": "<string>",
"plan": "free",
"created_at": "2023-11-07T05:31:56Z"
},
"app": {
"id": "app_01JB8Z0A1C3E5G7J9K1M3P5R7T",
"account": "acct_01JB8YZ3N2Q4R6T8V0X2Z4B6D8",
"name": "<string>",
"api_version": "2023-12-25",
"settings": {
"transcription": true,
"auto_read": true
},
"created_at": "2023-11-07T05:31:56Z",
"sandbox_join_code": "brave-otter-40718263"
},
"claimed": true,
"allowance": {
"tier": "anonymous",
"scope": "app",
"channels": [
"telegram"
],
"contacts": {
"limit": 1,
"used": 1
},
"messages_per_contact": 1,
"messages": {
"limit": 1,
"used": 1,
"remaining": 1
},
"upgrade": "<string>",
"expires_at": "2023-11-07T05:31:56Z"
},
"user": {
"name": "<string>",
"provider": "github",
"email": "jsmith@example.com"
}
}{
"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": "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>"
}
}{
"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>"
}
}Poll a device sign-in for its key
Answers the state of a sign-in started with POST /v1/device/authorizations.
While the person has not finished, status is pending: wait interval
seconds and poll again. Polling faster answers 429 rate_limited with
retry_after. Once they approve, status is approved and the answer
holds a new fk_test_ key, shown once; the device code is then used up,
and later polls answer expired. If the sign-in claimed a sandbox app,
that app’s sandbox keys stop working when this key is handed out: replace
FLOW_MESSAGING_KEY with it. (A claim through claim_url in a browser
hands out no key, and revokes the sandbox key unless the person chooses to
keep their agent’s key working.) denied means the person refused, and
expired that the code ran out (after expires_in seconds): start again.
Polls are also limited per client network, unknown codes included.
curl --request POST \
--url https://api.flow.engineer/v1/device/token \
--header 'Content-Type: application/json' \
--data '
{
"device_code": "<string>"
}
'const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({device_code: '<string>'})
};
fetch('https://api.flow.engineer/v1/device/token', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.flow.engineer/v1/device/token"
payload = { "device_code": "<string>" }
headers = {"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/token"
payload := strings.NewReader("{\n \"device_code\": \"<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))
}{
"status": "pending",
"interval": 2,
"key": "<string>",
"api_key": {
"id": "key_01JB8Z1B2D4F6H8K0M2P4R6T8W",
"mode": "test",
"last4": "<string>",
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z"
},
"account": {
"id": "acct_01JB8YZ3N2Q4R6T8V0X2Z4B6D8",
"name": "<string>",
"plan": "free",
"created_at": "2023-11-07T05:31:56Z"
},
"app": {
"id": "app_01JB8Z0A1C3E5G7J9K1M3P5R7T",
"account": "acct_01JB8YZ3N2Q4R6T8V0X2Z4B6D8",
"name": "<string>",
"api_version": "2023-12-25",
"settings": {
"transcription": true,
"auto_read": true
},
"created_at": "2023-11-07T05:31:56Z",
"sandbox_join_code": "brave-otter-40718263"
},
"claimed": true,
"allowance": {
"tier": "anonymous",
"scope": "app",
"channels": [
"telegram"
],
"contacts": {
"limit": 1,
"used": 1
},
"messages_per_contact": 1,
"messages": {
"limit": 1,
"used": 1,
"remaining": 1
},
"upgrade": "<string>",
"expires_at": "2023-11-07T05:31:56Z"
},
"user": {
"name": "<string>",
"provider": "github",
"email": "jsmith@example.com"
}
}{
"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": "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>"
}
}{
"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>"
}
}Body
The device sign-in to poll.
The device_code from POST /v1/device/authorizations.
Response
The sign-in's state, with the key once it is approved.
A device sign-in's state. With approved, it carries a new test key (shown
once), the app it belongs to and whether a sandbox app was claimed.
pending: the person has not finished; poll again afterintervalseconds.approved: signed in;keyis set. The device code is now used up.denied: the person refused. Stop polling.expired: the codes ran out or were already used. Start again.
pending, approved, denied, expired Seconds to wait before the next poll.
x >= 1approved only. A new API key (fk_test_...), shown once; store it as FLOW_MESSAGING_KEY in place of the sandbox key. When the sign-in claimed a sandbox app, that app's sandbox keys stop working as this key is handed out.
An API key's record. The key itself is shown once, at creation, and stored only as a hash.
Show child attributes
Show child attributes
A customer company. Holds the plan, billing and members. Every app belongs to one account.
Show child attributes
Show child attributes
One agent integration. Owns API keys (per mode), webhook endpoints and settings.
Show child attributes
Show child attributes
approved only. true when the sign-in claimed the app of the claim_token or key it was started with.
What the app may still send on the shared sandbox senders for free. Present
only on apps that have one: apps made with POST /v1/sandbox/keys
(anonymous, one allowance per app), and apps of people who signed in
(signed_in, one allowance per person: every app the person owns or claimed
draws on the same contacts and messages, so the counts here are the
person's, over all those apps). Only messages your agent sends count, on the
channels in channels; inbound messages are free. A contact counts once it
joins an app on a sandbox sender, and keeps counting after it leaves. Sends
past the allowance answer 403 permission with channel_code
sandbox_allowance_used; a join past contacts.limit is refused in the
chat.
Show child attributes
Show child attributes
The person who approved a device sign-in.
Show child attributes
Show child attributes