Using a FlowLink
Send deposits to trigger automatic conversion and delivery, manage the FlowLink lifecycle (pause, resume, disable), and read transaction history. Examples in Go, TypeScript, Java, Rust, and Python.
Sending deposits
Using a FlowLink is simple: send the source asset to the deposit_address returned at creation. No further API calls are required — the deposit triggers the whole flow automatically.
When a deposit arrives at an active FlowLink, Movmint:
- detects and accepts the deposit and records it on the ledger,
- screens it via TRM Labs (sanctions/risk),
- converts it at the current market rate with your client-specific spread and fees, and
- delivers the target asset to the preconfigured destination (DLT transfer, push-to-card, or ACH/FedNow bank transfer).
Each deposit is processed as an independent transaction; concurrent deposits are queued and processed sequentially. Because pricing happens at deposit time, there is no quote to lock or capture.
Delivery timingAs with Quote & Capture delivery, wall-clock time depends on the destination rail: DLT delivery is bounded by network confirmation, push-to-card is minutes to hours, and bank (ACH/FedNow) ranges from near-instant to a few business days. A deposit flagged by screening is held for compliance review before delivery.
What happens to deposits when not active
| FlowLink status | Behavior on deposit |
|---|---|
active | Processed straight-through. |
paused | Held; processed when the FlowLink is resumed (you are alerted that a deposit arrived at a paused FlowLink). |
disabled | The address is decommissioned; any held funds are returned per policy. |
Managing the lifecycle
Three endpoints move a FlowLink between states. All are POST, take the flowlink_id in the path, require an x-idempotency-key header, and return the updated FlowLink object.
| Action | Endpoint | Valid from | Result |
|---|---|---|---|
| Pause | POST /flowlink/{flowlink_id}/pause | active | paused — deposits are held. |
| Resume | POST /flowlink/{flowlink_id}/resume | paused | active — held deposits are queued for processing. |
| Disable | POST /flowlink/{flowlink_id}/disable | active or paused | disabled — permanent; create a new FlowLink to resume use. |
Calling an action from the wrong state returns 409 (e.g., resuming a FlowLink that is not paused). An unknown flowlink_id returns 404.
Lifecycle code examples
The examples define a single helper that performs any lifecycle action (pause, resume, or disable).
cURL
FLOWLINK_ID="2b7a1f4c-9d8e-4a3b-bc12-3456789abcde"
# Pause
curl -X POST "https://api.sandbox.movmint.io/flowlink/$FLOWLINK_ID/pause" \
-H "Authorization: Bearer <access_token>" -H "x-idempotency-key: $(uuidgen)"
# Resume
curl -X POST "https://api.sandbox.movmint.io/flowlink/$FLOWLINK_ID/resume" \
-H "Authorization: Bearer <access_token>" -H "x-idempotency-key: $(uuidgen)"
# Disable
curl -X POST "https://api.sandbox.movmint.io/flowlink/$FLOWLINK_ID/disable" \
-H "Authorization: Bearer <access_token>" -H "x-idempotency-key: $(uuidgen)"Go
package main
import (
"fmt"
"io"
"log"
"net/http"
"github.com/google/uuid"
)
const baseURL = "https://api.sandbox.movmint.io"
// action is one of "pause", "resume", "disable".
func lifecycle(token, flowlinkID, action string) (string, error) {
url := fmt.Sprintf("%s/flowlink/%s/%s", baseURL, flowlinkID, action)
req, _ := http.NewRequest(http.MethodPost, url, nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("x-idempotency-key", uuid.NewString())
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("%s failed (%d): %s", action, resp.StatusCode, body)
}
return string(body), nil
}
func main() {
out, err := lifecycle("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde", "pause")
if err != nil {
log.Fatal(err)
}
fmt.Println(out)
}TypeScript
import { randomUUID } from "crypto";
const BASE_URL = "https://api.sandbox.movmint.io";
type Action = "pause" | "resume" | "disable";
async function lifecycle(token: string, flowlinkId: string, action: Action) {
const res = await fetch(`${BASE_URL}/flowlink/${flowlinkId}/${action}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"x-idempotency-key": randomUUID(),
},
});
if (!res.ok) {
throw new Error(`${action} failed (${res.status}): ${await res.text()}`);
}
return res.json();
}
console.log(await lifecycle("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde", "pause"));Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;
public class FlowLinkLifecycle {
private static final String BASE_URL = "https://api.sandbox.movmint.io";
// action is one of "pause", "resume", "disable".
static String lifecycle(String token, String flowlinkId, String action) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/flowlink/" + flowlinkId + "/" + action))
.header("Authorization", "Bearer " + token)
.header("x-idempotency-key", UUID.randomUUID().toString())
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException(action + " failed (" + response.statusCode()
+ "): " + response.body());
}
return response.body();
}
public static void main(String[] args) throws Exception {
System.out.println(lifecycle("<access_token>",
"2b7a1f4c-9d8e-4a3b-bc12-3456789abcde", "pause"));
}
}Rust
use uuid::Uuid;
const BASE_URL: &str = "https://api.sandbox.movmint.io";
// action is one of "pause", "resume", "disable".
async fn lifecycle(token: &str, flowlink_id: &str, action: &str)
-> Result<serde_json::Value, Box<dyn std::error::Error>>
{
let client = reqwest::Client::new();
let resp = client
.post(format!("{BASE_URL}/flowlink/{flowlink_id}/{action}"))
.bearer_auth(token)
.header("x-idempotency-key", Uuid::new_v4().to_string())
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
return Err(format!("{action} failed ({status}): {}", resp.text().await?).into());
}
Ok(resp.json().await?)
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let fl = lifecycle("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde", "pause").await?;
println!("{}", serde_json::to_string_pretty(&fl)?);
Ok(())
}Python
import uuid
import requests
BASE_URL = "https://api.sandbox.movmint.io"
def lifecycle(token: str, flowlink_id: str, action: str) -> dict:
"""action is one of 'pause', 'resume', 'disable'."""
resp = requests.post(
f"{BASE_URL}/flowlink/{flowlink_id}/{action}",
headers={
"Authorization": f"Bearer {token}",
"x-idempotency-key": str(uuid.uuid4()),
},
timeout=30,
)
resp.raise_for_status()
return resp.json()
print(lifecycle("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde", "pause"))Reading transaction history
GET /flowlink/{flowlink_id}/transactions returns the executions for a FlowLink. Each record links the originating deposit, the market-rate conversion (rate, spread, fees), and the delivery.
| Parameter | In | Required | Description |
|---|---|---|---|
flowlink_id | path | yes | The FlowLink to query. |
status | query | no | Filter by transaction status (see below). |
from / to | query | no | date-time bounds (inclusive). |
limit | query | no | Page size, 1–200 (default 50). |
cursor | query | no | Opaque cursor from a previous response's next_cursor. |
x-idempotency-key | header | yes | A UUID you generate. |
Transaction status lifecycle
| Status | Meaning |
|---|---|
PENDING | Deposit registered, processing not yet started. |
DEPOSIT_RECEIVED | Deposit detected and accepted. |
COMPLIANCE_HOLD | Held by TRM screening, pending compliance review. |
HELD | Held (e.g., arrived at a paused FlowLink). |
CONVERTING | FX conversion in progress. |
DELIVERING | Delivering the target asset to the destination. |
COMPLETED | Delivered and settled. |
FAILED | Failed at some step. |
RETURNED | Funds returned to source. |
Transaction code examples
cURL
curl "https://api.sandbox.movmint.io/flowlink/2b7a1f4c-9d8e-4a3b-bc12-3456789abcde/transactions?status=COMPLETED&limit=50" \
-H "Authorization: Bearer <access_token>" \
-H "x-idempotency-key: $(uuidgen)"Go
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"github.com/google/uuid"
)
const baseURL = "https://api.sandbox.movmint.io"
func main() {
token := "<access_token>"
flowlinkID := "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde"
q := url.Values{}
q.Set("status", "COMPLETED")
q.Set("limit", "50")
endpoint := fmt.Sprintf("%s/flowlink/%s/transactions?%s", baseURL, flowlinkID, q.Encode())
req, _ := http.NewRequest(http.MethodGet, endpoint, nil)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("x-idempotency-key", uuid.NewString())
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
log.Fatalf("get transactions failed (%d): %s", resp.StatusCode, body)
}
fmt.Println(string(body))
}TypeScript
import { randomUUID } from "crypto";
const BASE_URL = "https://api.sandbox.movmint.io";
async function flowLinkTransactions(token: string, flowlinkId: string) {
const url = new URL(`${BASE_URL}/flowlink/${flowlinkId}/transactions`);
url.searchParams.set("status", "COMPLETED");
url.searchParams.set("limit", "50");
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
"x-idempotency-key": randomUUID(),
},
});
if (!res.ok) {
throw new Error(`Get transactions failed (${res.status}): ${await res.text()}`);
}
return res.json();
}
console.log(await flowLinkTransactions("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde"));Java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.UUID;
public class FlowLinkTransactions {
private static final String BASE_URL = "https://api.sandbox.movmint.io";
public static void main(String[] args) throws Exception {
String token = "<access_token>";
String flowlinkId = "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/flowlink/" + flowlinkId
+ "/transactions?status=COMPLETED&limit=50"))
.header("Authorization", "Bearer " + token)
.header("x-idempotency-key", UUID.randomUUID().toString())
.GET()
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Get transactions failed (" + response.statusCode()
+ "): " + response.body());
}
System.out.println(response.body());
}
}Rust
use uuid::Uuid;
const BASE_URL: &str = "https://api.sandbox.movmint.io";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = "<access_token>";
let flowlink_id = "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde";
let client = reqwest::Client::new();
let resp = client
.get(format!("{BASE_URL}/flowlink/{flowlink_id}/transactions"))
.query(&[("status", "COMPLETED"), ("limit", "50")])
.bearer_auth(token)
.header("x-idempotency-key", Uuid::new_v4().to_string())
.send()
.await?;
if !resp.status().is_success() {
let status = resp.status();
return Err(format!("get transactions failed ({status}): {}", resp.text().await?).into());
}
let data: serde_json::Value = resp.json().await?;
println!("{}", serde_json::to_string_pretty(&data)?);
Ok(())
}Python
import uuid
import requests
BASE_URL = "https://api.sandbox.movmint.io"
def flowlink_transactions(token: str, flowlink_id: str, **filters) -> dict:
resp = requests.get(
f"{BASE_URL}/flowlink/{flowlink_id}/transactions",
headers={
"Authorization": f"Bearer {token}",
"x-idempotency-key": str(uuid.uuid4()),
},
params={"status": "COMPLETED", "limit": 50, **filters},
timeout=30,
)
resp.raise_for_status()
return resp.json()
print(flowlink_transactions("<access_token>", "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde"))Example response (200)
200){
"data": [
{
"transaction_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"flowlink_id": "2b7a1f4c-9d8e-4a3b-bc12-3456789abcde",
"status": "COMPLETED",
"deposit": {
"tx_id": "0xdeadbeef...",
"asset": "USDC",
"network": "ethereum",
"amount": 5000.00,
"sender_address": "0xsender...",
"received_at": "2026-06-24T12:10:00Z"
},
"conversion": {
"from_asset": "USDC",
"to_asset": "USD",
"market_rate": 1.0,
"spread": 0.0015,
"fees": 2.50,
"input_amount": 5000.00,
"output_amount": 4990.00,
"executed_at": "2026-06-24T12:10:20Z"
},
"delivery": {
"destination_type": "BANK_ACCOUNT",
"asset": "USD",
"amount": 4990.00,
"provider_reference": "ach_01H...",
"delivered_at": "2026-06-24T12:11:00Z"
},
"created_at": "2026-06-24T12:10:00Z",
"updated_at": "2026-06-24T12:11:00Z"
}
],
"next_cursor": null
}Each transaction links the deposit, the conversion (with market_rate, spread, fees, and input/output amounts), and the delivery (with a provider_reference such as an ACH/FedNow payment ID, an iPayout transfer ID, or a DLT transfer ID). Page through results using next_cursor until it is null.
Error responses
| HTTP | Cause |
|---|---|
404 | FlowLink not found. |
500 | Internal server error — retry with backoff. |
Updated 2 months ago
