Authentication & Authorization
Movmint FX Services uses the OAuth 2.0 Client Credentials flow. Exchange the client_id and client_secret issued during onboarding for a short-lived bearer token, then send it on every API request.
Overview
Every Movmint FX Services request (other than the token request itself) is authenticated with an OAuth 2.0 bearer token. You obtain that token using the Client Credentials grant — a machine-to-machine flow that exchanges the client_id and client_secret issued during Technical Onboarding for a time-limited access_token.
Authorization is implicit in the token: it is scoped to your organization and to the Products you are subscribed to. You do not pass an organization identifier on requests — Movmint derives it from your credentials.
sequenceDiagram
participant App as Your Application
participant Auth as POST /oauth2/token
participant API as Movmint FX API
App->>Auth: client_id + client_secret (grant_type=client_credentials)
Auth-->>App: access_token (Bearer, expires_in seconds)
App->>API: Request + Authorization: Bearer <access_token>
API-->>App: Response
Step 1 — Request an access token
Send a POST to /oauth2/token with a application/x-www-form-urlencoded body.
Endpoint
POST https://api.sandbox.movmint.io/oauth2/token (Sandbox)
POST https://api.movmint.io/oauth2/token (Production)
Form parameters
| Parameter | Required | Value |
|---|---|---|
grant_type | yes | Must be client_credentials. |
client_id | yes | The client ID issued during onboarding. |
client_secret | yes | The client secret issued during onboarding. |
scope | yes | Must be openid. |
cURL
curl -X POST https://api.sandbox.movmint.io/oauth2/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=openid"Successful response (200)
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid"
}| Field | Description |
|---|---|
access_token | The JWT to send in the Authorization header of subsequent requests. |
token_type | Always Bearer. |
expires_in | Seconds until the token expires. Cache the token and refresh before it lapses. |
scope | The granted scope (openid). |
Error responses
| HTTP | Cause |
|---|---|
400 | Invalid request parameters (e.g., missing or incorrect grant_type). |
401 | Authentication failed (invalid client_id or client_secret). |
500 | Internal server error — retry with backoff. |
Step 2 — Call the API with your token
Send the token in the Authorization header on every request:
Authorization: Bearer <access_token>
A missing or invalid token is rejected with 401 Unauthorized. The example below requests an FX quote (see FX Services › Quote for the full schema).
Code examples
The following examples request a token and then use it to call a protected endpoint. They target Sandbox; swap the base URL to https://api.movmint.io for Production.
Go
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
)
const baseURL = "https://api.sandbox.movmint.io"
type tokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
func getToken(clientID, clientSecret string) (tokenResponse, error) {
form := url.Values{}
form.Set("grant_type", "client_credentials")
form.Set("client_id", clientID)
form.Set("client_secret", clientSecret)
form.Set("scope", "openid")
resp, err := http.Post(
baseURL+"/oauth2/token",
"application/x-www-form-urlencoded",
strings.NewReader(form.Encode()),
)
if err != nil {
return tokenResponse{}, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return tokenResponse{}, fmt.Errorf("token request failed (%d): %s", resp.StatusCode, body)
}
var tok tokenResponse
if err := json.Unmarshal(body, &tok); err != nil {
return tokenResponse{}, err
}
return tok, nil
}
func main() {
tok, err := getToken("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
if err != nil {
log.Fatal(err)
}
fmt.Printf("token expires in %d seconds\n", tok.ExpiresIn)
// Use tok.AccessToken in the Authorization header of subsequent requests.
}TypeScript
const BASE_URL = "https://api.sandbox.movmint.io";
interface TokenResponse {
access_token: string;
token_type: string;
expires_in: number;
scope: string;
}
async function getToken(clientId: string, clientSecret: string): Promise<TokenResponse> {
const body = new URLSearchParams({
grant_type: "client_credentials",
client_id: clientId,
client_secret: clientSecret,
scope: "openid",
});
const res = await fetch(`${BASE_URL}/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body,
});
if (!res.ok) {
throw new Error(`Token request failed (${res.status}): ${await res.text()}`);
}
return (await res.json()) as TokenResponse;
}
const token = await getToken("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
console.log(`token expires in ${token.expires_in} seconds`);
// Use token.access_token in the Authorization header of subsequent requests.Java
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class Auth {
private static final String BASE_URL = "https://api.sandbox.movmint.io";
static String form(String k, String v) {
return URLEncoder.encode(k, StandardCharsets.UTF_8) + "="
+ URLEncoder.encode(v, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
String body = String.join("&",
form("grant_type", "client_credentials"),
form("client_id", "YOUR_CLIENT_ID"),
form("client_secret", "YOUR_CLIENT_SECRET"),
form("scope", "openid"));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/oauth2/token"))
.header("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Token request failed (" + response.statusCode()
+ "): " + response.body());
}
// Parse response.body() with your JSON library to read access_token.
System.out.println(response.body());
}
}Rust
use serde::Deserialize;
const BASE_URL: &str = "https://api.sandbox.movmint.io";
#[derive(Deserialize, Debug)]
struct TokenResponse {
access_token: String,
token_type: String,
expires_in: i64,
scope: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let params = [
("grant_type", "client_credentials"),
("client_id", "YOUR_CLIENT_ID"),
("client_secret", "YOUR_CLIENT_SECRET"),
("scope", "openid"),
];
let client = reqwest::Client::new();
let resp = client
.post(format!("{BASE_URL}/oauth2/token"))
.form(¶ms)
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().await?;
return Err(format!("token request failed ({status}): {text}").into());
}
let token: TokenResponse = resp.json().await?;
println!("token expires in {} seconds", token.expires_in);
// Use token.access_token in the Authorization header of subsequent requests.
Ok(())
}Python
import requests
BASE_URL = "https://api.sandbox.movmint.io"
def get_token(client_id: str, client_secret: str) -> dict:
resp = requests.post(
f"{BASE_URL}/oauth2/token",
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
"scope": "openid",
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=30,
)
resp.raise_for_status()
return resp.json()
token = get_token("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
print(f"token expires in {token['expires_in']} seconds")
# Use token["access_token"] in the Authorization header of subsequent requests.Best practices
- Cache and reuse tokens. A token is valid for
expires_inseconds. Request one token and reuse it across requests rather than fetching a new one each call. Refresh shortly before expiry (e.g., when less than 60 seconds remain). - Handle
401by re-authenticating. If a request returns401, your token has likely expired or been revoked — fetch a new one and retry once. - Protect the secret. Keep
client_secretin a secrets manager or environment variable. Never expose it in browsers, mobile apps, logs, or version control. - Separate environments. Use Sandbox credentials only against
api.sandbox.movmint.ioand Production credentials only againstapi.movmint.io.
Next steps
With a bearer token in hand, continue to FX Services to request and capture your first quote.
Updated 2 months ago
