curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/oauth/token' \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=ac_8Xgcqs0ZRUe0M57eyADP9yT4' \
--data-urlencode 'redirect_uri=http://127.0.0.1:33418/oauth/callback' \
--data-urlencode 'code_verifier=6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o' \
--data-urlencode 'client_id=cl_ra8t5tsberxpax0351jdyqgrb7' \
--data-urlencode 'resource=https://app.reflections.ai/mcp'import { exit } from "node:process";
const response = await fetch("https://app.reflections.ai/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
"grant_type": "authorization_code",
"code": "ac_8Xgcqs0ZRUe0M57eyADP9yT4",
"redirect_uri": "http://127.0.0.1:33418/oauth/callback",
"code_verifier": "6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o",
"client_id": "cl_ra8t5tsberxpax0351jdyqgrb7",
"resource": "https://app.reflections.ai/mcp"
}),
signal: AbortSignal.timeout(30_000),
});
const responseBody = await response.text();
if (!response.ok) {
console.error(`HTTP ${response.status}: ${responseBody}`);
exit(1);
}
console.log(responseBody);
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
data = urlencode({
"grant_type": "authorization_code",
"code": "ac_8Xgcqs0ZRUe0M57eyADP9yT4",
"redirect_uri": "http://127.0.0.1:33418/oauth/callback",
"code_verifier": "6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o",
"client_id": "cl_ra8t5tsberxpax0351jdyqgrb7",
"resource": "https://app.reflections.ai/mcp",
}).encode("utf-8")
request = Request("https://app.reflections.ai/oauth/token",
method="POST", headers=headers, data=data)
try:
with urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
except HTTPError as error:
error_body = error.read().decode("utf-8")
raise RuntimeError(f"HTTP {error.code}: {error_body}") from error
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
form := url.Values{
"grant_type": {"authorization_code"},
"code": {"ac_8Xgcqs0ZRUe0M57eyADP9yT4"},
"redirect_uri": {"http://127.0.0.1:33418/oauth/callback"},
"code_verifier": {"6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o"},
"client_id": {"cl_ra8t5tsberxpax0351jdyqgrb7"},
"resource": {"https://app.reflections.ai/mcp"},
}
body := strings.NewReader(form.Encode())
request, err := http.NewRequest("POST", "https://app.reflections.ai/oauth/token", body)
if err != nil {
panic(err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, responseBody))
}
fmt.Print(string(responseBody))
}
{
"access_token": "rat_x6U8znpCLsAr4BolEF6a8WP4tYxIDW6A",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rrt_Hq2Lw7Vb4Nc9Xk1Mz6Tr3Ys8Pd5Gf0Aj",
"scope": "talent:read licenses:read checks:write generations:write",
"livemode": false
}Create an access token
Planned contract — unavailable in test and live modes. This operation is a protocol reference, not a callable endpoint. Start with the working sandbox quickstart.
Exchanges a grant for an access token, as OAuth 2.0 (RFC 6749) defines. The body is form-encoded.
authorization_code: an MCP client redeems the code from the authorization page with itscode_verifier. It receives aBeareraccess token for the MCP server, valid for one hour, and a refresh token.refresh_token: exchanges a refresh token for a new access token and a new refresh token. The old refresh token stops working. Sendscopeto narrow the new token.client_credentials: an RSL client requests aLicensetoken for a talent’s facet, sending the RSL<license>it wants aslicenseand the facet’s IRI asresource. Reflections issues it only when your account holds a license that covers the usage, or the talent’s posture allows the usage without one. ALicensetoken is not an authorization to generate: check the use withPOST /v1/checksbefore you generate.
A code issued to a platform’s client_id is refused here with unauthorized_client:
platforms redeem codes at POST /v1/connections
and act with their secret key, not with access tokens.
The token endpoint takes no key. A public client, such as a desktop agent, sends its
client_id in the body. A confidential client authenticates with its client ID and
secret, and client_credentials needs one. Errors are OAuth errors (RFC 6749 §5.2), not
problem details; 429 and 5xx responses are problem details, as everywhere else.
curl --fail-with-body --max-time 30 -X POST 'https://app.reflections.ai/oauth/token' \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=ac_8Xgcqs0ZRUe0M57eyADP9yT4' \
--data-urlencode 'redirect_uri=http://127.0.0.1:33418/oauth/callback' \
--data-urlencode 'code_verifier=6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o' \
--data-urlencode 'client_id=cl_ra8t5tsberxpax0351jdyqgrb7' \
--data-urlencode 'resource=https://app.reflections.ai/mcp'import { exit } from "node:process";
const response = await fetch("https://app.reflections.ai/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
"grant_type": "authorization_code",
"code": "ac_8Xgcqs0ZRUe0M57eyADP9yT4",
"redirect_uri": "http://127.0.0.1:33418/oauth/callback",
"code_verifier": "6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o",
"client_id": "cl_ra8t5tsberxpax0351jdyqgrb7",
"resource": "https://app.reflections.ai/mcp"
}),
signal: AbortSignal.timeout(30_000),
});
const responseBody = await response.text();
if (!response.ok) {
console.error(`HTTP ${response.status}: ${responseBody}`);
exit(1);
}
console.log(responseBody);
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
headers = {
"Content-Type": "application/x-www-form-urlencoded",
}
data = urlencode({
"grant_type": "authorization_code",
"code": "ac_8Xgcqs0ZRUe0M57eyADP9yT4",
"redirect_uri": "http://127.0.0.1:33418/oauth/callback",
"code_verifier": "6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o",
"client_id": "cl_ra8t5tsberxpax0351jdyqgrb7",
"resource": "https://app.reflections.ai/mcp",
}).encode("utf-8")
request = Request("https://app.reflections.ai/oauth/token",
method="POST", headers=headers, data=data)
try:
with urlopen(request, timeout=30) as response:
print(response.read().decode("utf-8"))
except HTTPError as error:
error_body = error.read().decode("utf-8")
raise RuntimeError(f"HTTP {error.code}: {error_body}") from error
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
form := url.Values{
"grant_type": {"authorization_code"},
"code": {"ac_8Xgcqs0ZRUe0M57eyADP9yT4"},
"redirect_uri": {"http://127.0.0.1:33418/oauth/callback"},
"code_verifier": {"6l_CpK7wrBmjR_geDznNZuKR9du8tNl4p8wxTb6jJ9o"},
"client_id": {"cl_ra8t5tsberxpax0351jdyqgrb7"},
"resource": {"https://app.reflections.ai/mcp"},
}
body := strings.NewReader(form.Encode())
request, err := http.NewRequest("POST", "https://app.reflections.ai/oauth/token", body)
if err != nil {
panic(err)
}
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{Timeout: 30 * time.Second}
response, err := client.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
responseBody, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode < 200 || response.StatusCode >= 300 {
panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, responseBody))
}
fmt.Print(string(responseBody))
}
{
"access_token": "rat_x6U8znpCLsAr4BolEF6a8WP4tYxIDW6A",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rrt_Hq2Lw7Vb4Nc9Xk1Mz6Tr3Ys8Pd5Gf0Aj",
"scope": "talent:read licenses:read checks:write generations:write",
"livemode": false
}Authorizations
A confidential OAuth client's client_id and client_secret, sent as HTTP Basic
credentials. Register the client with your secret key to receive them; see
POST /oauth/register.
Body
- Authorization code
- Refresh token
- RSL license token
A token request. Its fields follow from grant_type.
authorization_code.
authorization_code The code the redirect carried.
The redirect_uri sent to the authorization page.
The PKCE verifier whose S256 hash was the code_challenge.
43 - 128The client's ID. Required for a public client, which sends no secret.
The resource the token is for, as RFC 8707 defines, https://app.reflections.ai/mcp.
Response
The access token.
An access token, as RFC 6749 returns it.
Send a Bearer token to the MCP server as Authorization: Bearer <token>. Send a
License token to the content host as Authorization: License <token>.
Bearer for the MCP server. License for an RSL license token.
Bearer, License Seconds until the access token expires: 3600 for Bearer. For License, 86400, or less
when the license it draws on ends sooner.
true when the token acts in live mode. For Bearer tokens, the person chose the mode on
the authorization page.
For authorization_code and refresh_token grants. Each use returns a new refresh token
and ends the old one. A refresh token expires after 30 days unused, or when the person
removes the client.
The space-separated scopes the token carries, which the person approved. Absent for License tokens.