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

ParameterRequiredValue
grant_typeyesMust be client_credentials.
client_idyesThe client ID issued during onboarding.
client_secretyesThe client secret issued during onboarding.
scopeyesMust 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"
}
FieldDescription
access_tokenThe JWT to send in the Authorization header of subsequent requests.
token_typeAlways Bearer.
expires_inSeconds until the token expires. Cache the token and refresh before it lapses.
scopeThe granted scope (openid).

Error responses

HTTPCause
400Invalid request parameters (e.g., missing or incorrect grant_type).
401Authentication failed (invalid client_id or client_secret).
500Internal 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(&params)
        .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_in seconds. 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 401 by re-authenticating. If a request returns 401, your token has likely expired or been revoked — fetch a new one and retry once.
  • Protect the secret. Keep client_secret in 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.io and Production credentials only against api.movmint.io.

Next steps

With a bearer token in hand, continue to FX Services to request and capture your first quote.


Did this page help you?