> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.crisscross.money/api-reference/collect/rate-locks/create-rate-lock/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.crisscross.money/_mcp/server. # Create a rate lock POST https://api.crisscross.money/v1/rate-locks Content-Type: application/json Locks the current reference rate for a currency pair so it can be reused across multiple merchant transactions until lock expiry or market-drift cancellation. The rate, markup, and validity window are fixed when the lock is created and stay constant for its lifetime, even if pricing changes afterwards. Pricing is mid-market reference + markup; the mid, the markup, and the resulting all-in rate are all returned. You can hold up to two active locks per currency pair, so you can pre-fetch a replacement (typically about an hour before the current lock expires) and overlap it with the lock it succeeds. While both are active, keep pricing existing browsing sessions off the current lock and send new sessions to the new one. If fewer than two locks are active a new one is minted (`201`); if two are already active the freshest existing lock is returned instead (`200`). Idempotency: **none**. Each mint creates a distinct lock, subject to the two-lock cap. Reference: https://docs.crisscross.money/api-reference/collect/rate-locks/create-rate-lock ## Authentication - `Authorization` header (bearer token, required) — Token obtained via the login flow. ## Request ### Body (application/json) This endpoint expects a RateLockRequest. - `base` (string, required) — Base currency of the pair (ISO 4217) — the currency you price in, e.g. `USD`. - `quote` (string, required) — Quote currency of the pair (ISO 4217) — the currency you collect in, e.g. `KES`. ## Response ### 200 An existing active rate lock, returned because two active locks already exist for this currency pair (the maximum of two). The freshest of the two is returned. - `rateLockId` (string, required) - `status` (enum, required) — - `active` — usable. - `expired` — past `expiresAt`. Terminal. - `cancelled` — invalidated before expiry; see `cancellationReason`. Terminal. `expired` and `cancelled` are permanent — a lock never returns to `active`. A lock at or past `expiresAt` is invalid even if `status` still reads `active`. - Allowed values: `active`, `expired`, `cancelled` - `base` (string, required) — Base currency (ISO 4217). - `quote` (string, required) — Quote currency (ISO 4217). - `midRate` (string, required) — Reference mid-market rate the lock was priced from. `1 base = quote`. - `markupBps` (integer, required) — The markup applied over the mid, in basis points (100 bps = 1%). - `allInRate` (string, required) — The rate you transact at: mid plus markup. This is the rate carried onto every transaction that uses the lock. `1 base = quote`. - `createdAt` (datetime, required) - `expiresAt` (datetime, required) — End of the lock's validity window. The lock is invalid from this time even if `status` still reads `active`. - `cancellationReason` (enum, optional) — Why the lock was cancelled. Present only when `status` is `cancelled`. `market_drift` — the rate moved beyond the allowed tolerance. `requested` — you cancelled the lock yourself via the cancel endpoint. - Allowed values: `market_drift`, `requested` ### 201 A newly minted active rate lock (a lock slot was available). - `rateLockId` (string, required) - `status` (enum, required) — - `active` — usable. - `expired` — past `expiresAt`. Terminal. - `cancelled` — invalidated before expiry; see `cancellationReason`. Terminal. `expired` and `cancelled` are permanent — a lock never returns to `active`. A lock at or past `expiresAt` is invalid even if `status` still reads `active`. - Allowed values: `active`, `expired`, `cancelled` - `base` (string, required) — Base currency (ISO 4217). - `quote` (string, required) — Quote currency (ISO 4217). - `midRate` (string, required) — Reference mid-market rate the lock was priced from. `1 base = quote`. - `markupBps` (integer, required) — The markup applied over the mid, in basis points (100 bps = 1%). - `allInRate` (string, required) — The rate you transact at: mid plus markup. This is the rate carried onto every transaction that uses the lock. `1 base = quote`. - `createdAt` (datetime, required) - `expiresAt` (datetime, required) — End of the lock's validity window. The lock is invalid from this time even if `status` still reads `active`. - `cancellationReason` (enum, optional) — Why the lock was cancelled. Present only when `status` is `cancelled`. `market_drift` — the rate moved beyond the allowed tolerance. `requested` — you cancelled the lock yourself via the cancel endpoint. - Allowed values: `market_drift`, `requested` ## Errors ### 400 Bad Request Error Bad request — validation or malformed request. - `message` (string, required) — Human-readable description of what went wrong. For logs and display — do not parse. - `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Not Found", "Unprocessable Entity", "Service Unavailable"). - `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth. ### 401 Unauthorized Error Unauthorized — missing or invalid token. - `message` (string, required) — Human-readable description of what went wrong. For logs and display — do not parse. - `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Not Found", "Unprocessable Entity", "Service Unavailable"). - `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth. ### 422 Unprocessable Entity Error The currency pair is not one we offer. - `message` (string, required) — Human-readable description of what went wrong. For logs and display — do not parse. - `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Not Found", "Unprocessable Entity", "Service Unavailable"). - `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth. ### 503 Service Unavailable Error Rates for this pair are temporarily unavailable — no live price right now. Retry later, or fall back to a per-session quote. - `message` (string, required) — Human-readable description of what went wrong. For logs and display — do not parse. - `error` (string, required) — Error category, typically the HTTP reason phrase (e.g. "Bad Request", "Unauthorized", "Not Found", "Unprocessable Entity", "Service Unavailable"). - `statusCode` (integer, required) — HTTP status code, mirroring the response header. The response header is the source of truth. ## Examples ### createRateLock_example **Request** ```json { "base": "USD", "quote": "KES" } ``` **Response** ```json { "rateLockId": "rlk_550e8400-e29b-41d4-a716-446655440000", "status": "active", "base": "USD", "quote": "KES", "midRate": "130.00", "markupBps": 200, "allInRate": "132.60", "createdAt": "2026-06-24T10:30:00Z", "expiresAt": "2026-06-24T22:30:00Z" } ``` **SDK Code** ```python createRateLock_example import requests url = "https://api.crisscross.money/v1/rate-locks" payload = { "base": "USD", "quote": "KES" } headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```javascript createRateLock_example const url = 'https://api.crisscross.money/v1/rate-locks'; const options = { method: 'POST', headers: {Authorization: 'Bearer ', 'Content-Type': 'application/json'}, body: '{"base":"USD","quote":"KES"}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go createRateLock_example package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.crisscross.money/v1/rate-locks" payload := strings.NewReader("{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby createRateLock_example require 'uri' require 'net/http' url = URI("https://api.crisscross.money/v1/rate-locks") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}" response = http.request(request) puts response.read_body ``` ```java createRateLock_example import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.crisscross.money/v1/rate-locks") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}") .asString(); ``` ```php createRateLock_example request('POST', 'https://api.crisscross.money/v1/rate-locks', [ 'body' => '{ "base": "USD", "quote": "KES" }', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', ], ]); echo $response->getBody(); ``` ```csharp createRateLock_example using RestSharp; var client = new RestClient("https://api.crisscross.money/v1/rate-locks"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift createRateLock_example import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [ "base": "USD", "quote": "KES" ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.crisscross.money/v1/rate-locks")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ``` ### Example 2 **Request** ```json { "base": "USD", "quote": "KES" } ``` **Response** ```json { "rateLockId": "rlk_550e8400-e29b-41d4-a716-446655440000", "status": "active", "base": "USD", "quote": "KES", "midRate": "130.00", "markupBps": 200, "allInRate": "132.60", "createdAt": "2024-01-15T09:30:00Z", "expiresAt": "2024-01-15T09:30:00Z", "cancellationReason": "market_drift" } ``` **SDK Code** ```python import requests url = "https://api.crisscross.money/v1/rate-locks" payload = { "base": "USD", "quote": "KES" } headers = { "Authorization": "Bearer ", "Content-Type": "application/json" } response = requests.post(url, json=payload, headers=headers) print(response.json()) ``` ```javascript const url = 'https://api.crisscross.money/v1/rate-locks'; const options = { method: 'POST', headers: {Authorization: 'Bearer ', 'Content-Type': 'application/json'}, body: '{"base":"USD","quote":"KES"}' }; try { const response = await fetch(url, options); const data = await response.json(); console.log(data); } catch (error) { console.error(error); } ``` ```go package main import ( "fmt" "strings" "net/http" "io" ) func main() { url := "https://api.crisscross.money/v1/rate-locks" payload := strings.NewReader("{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}") req, _ := http.NewRequest("POST", url, payload) req.Header.Add("Authorization", "Bearer ") req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := io.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body)) } ``` ```ruby require 'uri' require 'net/http' url = URI("https://api.crisscross.money/v1/rate-locks") http = Net::HTTP.new(url.host, url.port) http.use_ssl = true request = Net::HTTP::Post.new(url) request["Authorization"] = 'Bearer ' request["Content-Type"] = 'application/json' request.body = "{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}" response = http.request(request) puts response.read_body ``` ```java import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.Unirest; HttpResponse response = Unirest.post("https://api.crisscross.money/v1/rate-locks") .header("Authorization", "Bearer ") .header("Content-Type", "application/json") .body("{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}") .asString(); ``` ```php request('POST', 'https://api.crisscross.money/v1/rate-locks', [ 'body' => '{ "base": "USD", "quote": "KES" }', 'headers' => [ 'Authorization' => 'Bearer ', 'Content-Type' => 'application/json', ], ]); echo $response->getBody(); ``` ```csharp using RestSharp; var client = new RestClient("https://api.crisscross.money/v1/rate-locks"); var request = new RestRequest(Method.POST); request.AddHeader("Authorization", "Bearer "); request.AddHeader("Content-Type", "application/json"); request.AddParameter("application/json", "{\n \"base\": \"USD\",\n \"quote\": \"KES\"\n}", ParameterType.RequestBody); IRestResponse response = client.Execute(request); ``` ```swift import Foundation let headers = [ "Authorization": "Bearer ", "Content-Type": "application/json" ] let parameters = [ "base": "USD", "quote": "KES" ] as [String : Any] let postData = JSONSerialization.data(withJSONObject: parameters, options: []) let request = NSMutableURLRequest(url: NSURL(string: "https://api.crisscross.money/v1/rate-locks")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0) request.httpMethod = "POST" request.allHTTPHeaderFields = headers request.httpBody = postData as Data let session = URLSession.shared let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error as Any) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) } }) dataTask.resume() ```