Authentication
The SDK authenticates every request with an OAuth 2.0 bearer token. You choose how that token is obtained when you create the client (or bring one yourself via the OAuth 2.0 endpoints). Configure auth once; the SDK applies it — and, where the flow allows, refreshes it — for every request.
Choosing a flow
| Flow | Use it for | Client secret | SDK config |
|---|---|---|---|
| Authorization Code + PKCE | Interactive apps acting as a user — SPAs, CLIs, mobile/desktop | No | refreshToken (or bearer) |
| Client credentials | Confidential server-to-server machine access | Yes | clientCredentials |
| Token exchange | A partner's server acting in one sub-account | Yes | tokenExchange |
| Password grant | First-party scripts / quick dev | No | password |
| Bearer token | A token you already hold | — | bearer |
If your software acts on behalf of a human who logs in, use Authorization Code + PKCE. For an unattended backend, client credentials is the long-term answer (see the availability note below); until it ships, a password grant is the simplest first-party option.
Authorization Code + PKCE
The modern, secure flow for public clients (RFC 7636) — no client secret is ever stored. The user
authenticates with babelforce and approves your app; you then exchange the returned code (bound to
a one-time PKCE verifier) for an access token and a rotating refresh token. The SDK ships helpers for
each step (pkceChallenge / GeneratePKCE, buildAuthorizeUrl / BuildAuthorizeURL, and the
authorization-code exchange). How refresh tokens rotate, and what happens when one is used twice, is
explained under Refresh tokens.
- TypeScript
- Go
- Rust
// 1. Generate a PKCE verifier/challenge and send the user to the consent page.
const { codeVerifier, codeChallenge } = await pkceChallenge();
const consentUrl = buildAuthorizeUrl({
baseUrl: "https://services.babelforce.com",
clientId: "your-public-client-id",
redirectUri: "https://app.example.com/callback",
scope: "*",
codeChallenge,
state: "csrf-token",
});
// Redirect the user to `consentUrl`; they return to redirectUri?code=…&state=…
// 2. Exchange the code (with the same codeVerifier) for tokens.
const tokens = await authorizationCodeGrant({
baseUrl: "https://services.babelforce.com",
code: "code-from-the-redirect",
redirectUri: "https://app.example.com/callback",
clientId: "your-public-client-id",
codeVerifier,
});
// 3a. Long-lived client — the access token refreshes transparently (refresh tokens rotate):
const mgr = await ManagerClient.connect({
auth: { kind: "refreshToken", refreshToken: tokens.refresh_token!, clientId: "your-public-client-id" },
});
// 3b. …or a one-shot client straight from the access token:
// const mgr = await ManagerClient.connect({ auth: { kind: "bearer", token: tokens.access_token } });
// 1. Generate a PKCE verifier/challenge and send the user to the consent page.
pkce, _ := manager.GeneratePKCE()
consentURL := manager.BuildAuthorizeURL(manager.AuthorizeURLParams{
BaseURL: "https://services.babelforce.com",
ClientID: "your-public-client-id",
RedirectURI: "https://app.example.com/callback",
Scope: "*",
CodeChallenge: pkce.CodeChallenge,
State: "csrf-token",
})
_ = consentURL // redirect the user here; they return to RedirectURI?code=…&state=…
// 2. Exchange the code (public client → empty secret) for tokens.
tokens, _ := manager.AuthorizationCodeGrant(ctx, nil,
"https://services.babelforce.com",
"code-from-the-redirect",
"https://app.example.com/callback",
"your-public-client-id",
pkce.CodeVerifier,
"",
)
// 3. Long-lived client — the access token refreshes transparently (refresh tokens rotate):
mgr, _ := manager.Connect(ctx, manager.Options{
Auth: manager.RefreshToken(tokens.RefreshToken, "your-public-client-id"),
})
// Confidential clients send their secret with each exchange:
// Auth: manager.RefreshTokenWithSecret(tokens.RefreshToken, clientID, clientSecret)
// 1. Generate a PKCE verifier/challenge and send the user to the consent page.
let pkce = pkce_challenge();
let consent_url = build_authorize_url(
"https://services.babelforce.com",
"your-public-client-id",
"https://app.example.com/callback",
"*",
&pkce.code_challenge,
Some("csrf-token"),
None,
);
// Redirect the user to `consent_url`; they return to the redirect URI with a code.
// 2. Exchange the code via the OAuth token endpoint.
let mgr = ManagerClient::connect(Auth::Bearer { token: String::new() }).await?;
let tokens = mgr.auth.token("authorization_code", TokenRequest {
code: Some("code-from-the-redirect".into()),
redirect_uri: Some("https://app.example.com/callback".into()),
code_verifier: Some(pkce.code_verifier),
client_id: Some("your-public-client-id".into()),
..Default::default()
}).await?;
// 3a. Long-lived client — the access token refreshes transparently (refresh tokens rotate):
let mgr = ManagerClient::connect(Auth::RefreshToken {
refresh_token: tokens.refresh_token.expect("PKCE exchange returns a refresh token"),
client_id: "your-public-client-id".into(),
client_secret: None, // Some(secret) for refresh tokens issued to a confidential client
}).await?;
// 3b. …or a one-shot client straight from the access token (a static bearer never refreshes):
// let mgr = ManagerClient::connect(Auth::Bearer { token: tokens.access_token }).await?;
Rust refreshes the token automatically ~30 seconds before it expires for the password,
clientCredentials, and refreshToken flows (single-flight — concurrent requests share one
refresh, and rotating refresh tokens are tracked for you); a static Auth::Bearer token is never
refreshed. Token fetches go through the client's retry policy. To manage tokens yourself, the raw
mgr.auth.token("refresh_token", …) escape hatch remains — see
OAuth 2.0 endpoints.
Client credentials
An OAuth2 client_credentials grant for confidential, server-to-server access: the SDK exchanges
your clientId / clientSecret for a bearer token at /oauth/token and applies it to every
request. The token is refreshed transparently before it expires in all three languages.
The client_id / client_secret pair comes from an OAuth application of your account, created by
an owner or manager with oauthApplications.create (see the Partner API guide,
step 1). A token from such an application acts as a manager of the account and has no refresh token;
the SDK fetches a new one before it expires.
OAuth applications created through the API are being rolled out; ask babelforce support whether they are available for your account. Treat any client secret as highly sensitive — never commit it or expose it to a browser/mobile client. It is shown once, at creation or reset.
- TypeScript
- Go
- Rust
const mgr = await ManagerClient.connect({
auth: { kind: "clientCredentials", clientId, clientSecret },
});
mgr, _ := manager.Connect(ctx, manager.Options{
Auth: manager.ClientCredentials(clientID, clientSecret),
})
let mgr = ManagerClient::connect(Auth::ClientCredentials {
client_id, client_secret,
}).await?;
Password grant (legacy)
A first-party grant kept for interactive/dev use — it sends a username/password to /oauth/token
with the public manager client id (no secret). Prefer PKCE or client credentials for new
integrations. The token refreshes transparently in all three languages.
- TypeScript
- Go
- Rust
const mgr = await ManagerClient.connect({ auth: { kind: "password", user, pass } });
mgr, _ := manager.Connect(ctx, manager.Options{ Auth: manager.Password(user, pass) })
// Or against a custom OAuth2 application instead of the default "manager" client id:
// mgr, _ := manager.Connect(ctx, manager.Options{ Auth: manager.PasswordWithClientID(user, pass, clientID) })
let mgr = ManagerClient::connect(Auth::Password { user, pass, client_id: None }).await?;
// Or against a custom OAuth2 application instead of the default "manager" client id:
// let mgr = ManagerClient::connect(Auth::Password { user, pass, client_id: Some(client_id) }).await?;
Bearer token
If you already hold a valid access token — for example from the PKCE exchange above, or one minted elsewhere — pass it directly:
- TypeScript
- Go
- Rust
const mgr = await ManagerClient.connect({ auth: { kind: "bearer", token } });
mgr, _ := manager.Connect(ctx, manager.Options{ Auth: manager.Bearer(token) })
let mgr = ManagerClient::connect(Auth::Bearer { token }).await?;
OAuth 2.0 endpoints
The raw OAuth 2.0 endpoints — /oauth/authorize, /oauth/token, /oauth/revoke — are exposed as
mgr.auth / mgr.Auth for when you manage tokens yourself. They authenticate via credentials
carried in the request itself (per RFC 6749 / 7009), independent of the client's configured auth.
Token endpoint
Exchange a grant (authorization_code, refresh_token, password, or client_credentials) for an
access token.
- TypeScript
- Go
- Rust
const tokens = await mgr.auth.token({
grant_type: "client_credentials",
client_id: "…",
client_secret: "…",
scope: "…",
});
console.log(tokens.access_token, tokens.expires_in);
clientID, clientSecret := "…", "…"
tokens, _ := mgr.Auth.Token(ctx, authapi.OAuthTokenRequest{
GrantType: "client_credentials",
ClientId: &clientID,
ClientSecret: &clientSecret,
})
fmt.Println(tokens.AccessToken)
let tokens = mgr.auth.token("client_credentials", TokenRequest {
client_id: Some("…".into()),
client_secret: Some("…".into()),
scope: Some("…".into()),
..Default::default()
}).await?;
println!("{} {:?}", tokens.access_token, tokens.expires_in);
Authorize & revoke
- TypeScript
- Go
- Rust
// Authorization-code consent/redirect (for PKCE, prefer buildAuthorizeUrl above):
await mgr.auth.authorize({
response_type: "code",
client_id: "…",
redirect_uri: "https://app.example.com/callback",
scope: "…",
});
// Revoke an access or refresh token (RFC 7009):
await mgr.auth.revoke({ token: "…" });
_, _ = mgr.Auth.Authorize(ctx, authapi.AuthorizeParams{
ResponseType: "code",
ClientId: "…",
RedirectUri: "https://app.example.com/callback",
Scope: "…",
})
_ = mgr.Auth.Revoke(ctx, authapi.OAuthRevokeRequest{Token: "…"})
mgr.auth.authorize("code", "…", "https://app.example.com/callback", "…", None, None, None).await?;
mgr.auth.revoke("…", None, None, None).await?;
Grant failures
A failed grant — wrong credentials, an expired or already-used refresh token, an unavailable token
endpoint — surfaces as the SDK's typed API error, whether the SDK fetched the token transparently or
you called a grant helper yourself: ManagerApiError (status, code, body) in TypeScript,
*manager.APIError (Status, Code, Message, Body; errors.As compatible) in Go, and
ManagerError::Api { status, code, .. } in Rust. Branch on the structured status/code fields
rather than parsing the error message.
Base URL
The SDK talks to https://services.babelforce.com by default. Set baseUrl (TypeScript) /
BaseURL (Go) — or the builder's .base_url() (Rust) — to target a per-customer or non-production
host:
- TypeScript
- Go
- Rust
const mgr = await ManagerClient.connect({
baseUrl: "https://acme.babelforce.com",
auth: { kind: "clientCredentials", clientId, clientSecret },
});
mgr, _ := manager.Connect(ctx, manager.Options{
BaseURL: "https://acme.babelforce.com",
Auth: manager.ClientCredentials(clientID, clientSecret),
})
let mgr = ManagerClient::builder(Auth::ClientCredentials { client_id, client_secret })
.base_url("https://acme.babelforce.com")
.connect()
.await?;