curl --request POST \
--url https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--header 'X-Customer-IP: <x-customer-ip>' \
--data '
{
"revision": 156,
"fromCurrency": "MXN",
"toCurrency": "USD",
"customerToCurrencyAmount": 100000,
"vendorToCurrencyAmount": 100000,
"customerEndAt": "2024-02-23",
"vendorEndAt": "2024-02-23",
"rate": 20.616512,
"externalTransactionId": "780231b3ae0d7e9e5097nd89",
"extraFields": {
"field_a": "value_a",
"field_b": "value_b"
}
}
'import requests
url = "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote"
payload = {
"revision": 156,
"fromCurrency": "MXN",
"toCurrency": "USD",
"customerToCurrencyAmount": 100000,
"vendorToCurrencyAmount": 100000,
"customerEndAt": "2024-02-23",
"vendorEndAt": "2024-02-23",
"rate": 20.616512,
"externalTransactionId": "780231b3ae0d7e9e5097nd89",
"extraFields": {
"field_a": "value_a",
"field_b": "value_b"
}
}
headers = {
"X-Customer-IP": "<x-customer-ip>",
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Customer-IP': '<x-customer-ip>',
Authorization: 'Basic <encoded-value>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
revision: 156,
fromCurrency: 'MXN',
toCurrency: 'USD',
customerToCurrencyAmount: 100000,
vendorToCurrencyAmount: 100000,
customerEndAt: '2024-02-23',
vendorEndAt: '2024-02-23',
rate: 20.616512,
externalTransactionId: '780231b3ae0d7e9e5097nd89',
extraFields: {field_a: 'value_a', field_b: 'value_b'}
})
};
fetch('https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'revision' => 156,
'fromCurrency' => 'MXN',
'toCurrency' => 'USD',
'customerToCurrencyAmount' => 100000,
'vendorToCurrencyAmount' => 100000,
'customerEndAt' => '2024-02-23',
'vendorEndAt' => '2024-02-23',
'rate' => 20.616512,
'externalTransactionId' => '780231b3ae0d7e9e5097nd89',
'extraFields' => [
'field_a' => 'value_a',
'field_b' => 'value_b'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"Content-Type: application/json",
"X-Customer-IP: <x-customer-ip>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote"
payload := strings.NewReader("{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Customer-IP", "<x-customer-ip>")
req.Header.Add("Authorization", "Basic <encoded-value>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote")
.header("X-Customer-IP", "<x-customer-ip>")
.header("Authorization", "Basic <encoded-value>")
.header("Content-Type", "application/json")
.body("{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Customer-IP"] = '<x-customer-ip>'
request["Authorization"] = 'Basic <encoded-value>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "8173b9a7-ee61-413e-b9e3-7c04b2a067c5",
"hedgeIds": [
"4d78ac65-2c3f-47e2-8bf3-3f76124e9d27",
"5e89bd76-3d4f-58e3-9cf4-4g87235e0e38"
],
"transactionId": "4d78ac65-2c3f-47e2-8bf3-3f76124e9d27"
}{
"message": "REVISION_INVALID",
"reason": "The revision number is not valid or does not exist. Please fetch a new rate table using the /offline-rates endpoint and pass a valid revision."
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "REVISION_EXPIRED",
"reason": "The revision number has expired. Please fetch a new rate table using the /offline-rates endpoint."
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Could not parse IP address (v4 or v6) for ip"
}Accept
Accepts a dual hedge rate proposal and books both customer and supplier hedge legs at their proposed rates and tenors.
curl --request POST \
--url https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--header 'X-Customer-IP: <x-customer-ip>' \
--data '
{
"revision": 156,
"fromCurrency": "MXN",
"toCurrency": "USD",
"customerToCurrencyAmount": 100000,
"vendorToCurrencyAmount": 100000,
"customerEndAt": "2024-02-23",
"vendorEndAt": "2024-02-23",
"rate": 20.616512,
"externalTransactionId": "780231b3ae0d7e9e5097nd89",
"extraFields": {
"field_a": "value_a",
"field_b": "value_b"
}
}
'import requests
url = "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote"
payload = {
"revision": 156,
"fromCurrency": "MXN",
"toCurrency": "USD",
"customerToCurrencyAmount": 100000,
"vendorToCurrencyAmount": 100000,
"customerEndAt": "2024-02-23",
"vendorEndAt": "2024-02-23",
"rate": 20.616512,
"externalTransactionId": "780231b3ae0d7e9e5097nd89",
"extraFields": {
"field_a": "value_a",
"field_b": "value_b"
}
}
headers = {
"X-Customer-IP": "<x-customer-ip>",
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-Customer-IP': '<x-customer-ip>',
Authorization: 'Basic <encoded-value>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
revision: 156,
fromCurrency: 'MXN',
toCurrency: 'USD',
customerToCurrencyAmount: 100000,
vendorToCurrencyAmount: 100000,
customerEndAt: '2024-02-23',
vendorEndAt: '2024-02-23',
rate: 20.616512,
externalTransactionId: '780231b3ae0d7e9e5097nd89',
extraFields: {field_a: 'value_a', field_b: 'value_b'}
})
};
fetch('https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'revision' => 156,
'fromCurrency' => 'MXN',
'toCurrency' => 'USD',
'customerToCurrencyAmount' => 100000,
'vendorToCurrencyAmount' => 100000,
'customerEndAt' => '2024-02-23',
'vendorEndAt' => '2024-02-23',
'rate' => 20.616512,
'externalTransactionId' => '780231b3ae0d7e9e5097nd89',
'extraFields' => [
'field_a' => 'value_a',
'field_b' => 'value_b'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"Content-Type: application/json",
"X-Customer-IP: <x-customer-ip>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote"
payload := strings.NewReader("{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Customer-IP", "<x-customer-ip>")
req.Header.Add("Authorization", "Basic <encoded-value>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote")
.header("X-Customer-IP", "<x-customer-ip>")
.header("Authorization", "Basic <encoded-value>")
.header("Content-Type", "application/json")
.body("{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.grainfinance.co/v1/customers/{customerId}/dual-hedges/accept-offline-quote")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Customer-IP"] = '<x-customer-ip>'
request["Authorization"] = 'Basic <encoded-value>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"revision\": 156,\n \"fromCurrency\": \"MXN\",\n \"toCurrency\": \"USD\",\n \"customerToCurrencyAmount\": 100000,\n \"vendorToCurrencyAmount\": 100000,\n \"customerEndAt\": \"2024-02-23\",\n \"vendorEndAt\": \"2024-02-23\",\n \"rate\": 20.616512,\n \"externalTransactionId\": \"780231b3ae0d7e9e5097nd89\",\n \"extraFields\": {\n \"field_a\": \"value_a\",\n \"field_b\": \"value_b\"\n }\n}"
response = http.request(request)
puts response.read_body{
"id": "8173b9a7-ee61-413e-b9e3-7c04b2a067c5",
"hedgeIds": [
"4d78ac65-2c3f-47e2-8bf3-3f76124e9d27",
"5e89bd76-3d4f-58e3-9cf4-4g87235e0e38"
],
"transactionId": "4d78ac65-2c3f-47e2-8bf3-3f76124e9d27"
}{
"message": "REVISION_INVALID",
"reason": "The revision number is not valid or does not exist. Please fetch a new rate table using the /offline-rates endpoint and pass a valid revision."
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "REVISION_EXPIRED",
"reason": "The revision number has expired. Please fetch a new rate table using the /offline-rates endpoint."
}{
"message": "Can't perform this action",
"reason": "Supplied object is in *Cancelled* state, which prevents performing this action"
}{
"message": "Could not parse IP address (v4 or v6) for ip"
}Authorizations
Basic authentication using the partner API keys from https://console.grainfinance.co/keys
Headers
The IPv4 address of the browser from which the customer contacts your platform.
example: 192.158.1.38
((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}Path Parameters
The id of the customer within the Grain platform.
[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}Body
The payload required in order to accept an offline rate proposal and book the dual hedge.
The id of the revision that defines the accepted rate for the currency-pair and tenor.
156
The currency in which the transaction should be paid by your customer.
^[A-Z]{3}$"MXN"
The currency you want to buy.
^[A-Z]{3}$"USD"
The amount to hedge in the toCurrency for the customer.
100000
The amount to hedge in the toCurrency for the vendor.
100000
The date in which the customer hedge will be settled, denoted in YYYY-MM-DD format.
"2024-02-23"
The date in which the vendor hedge will be settled, denoted in YYYY-MM-DD format.
"2024-02-23"
Represents the offline hedge rate determined by a two-dimensional lookup based on the vendor and customer tenors.
Calculating Tenors:
Tenors are calculated from the calendar-day difference between the tenorCalculationDate and the vendor/customer end dates, then mapped into the nearest supported tenor bucket:
-
0 and ≤ 7 calendar days → 7-day tenor
-
7 and ≤ 14 calendar days → 14-day tenor
-
14 and ≤ 30 calendar days → 30-day tenor
-
30 and ≤ 60 calendar days → 60-day tenor
-
60 and ≤ 90 calendar days → 90-day tenor
- … continues in similar ranges up to 720 days
Note: All calendar days count — weekends and holidays included.
Rate Lookup Example:
The rate is retrieved from a two-dimensional map using both tenors:
"USDMXN": {
"7": { "7": 1.1 },
"14": { "7": 1.2, "14": 1.3 },
"30": { "7": 1.1, "14": 1.1, "30": 1.1 },
// ... and so on
}
In this example, if the vendor tenor is 14 days and the customer tenor is 7 days, the rate would be 1.2.
To calculate the difference in calendar days accurately, we recommend using a date manipulation library, such as date-fns and its differenceInCalendarDays function, or a similar feature provided by other date libraries.
20.616512
The transaction id, as identified within your system.
"780231b3ae0d7e9e5097nd89"
Additional fields that provide further details about the transaction. These fields should be specified as a valid JSON object.
Show child attributes
Show child attributes
{
"field_a": "value_a",
"field_b": "value_b"
}
Response
Dual Hedge Accepted
The response of accepting an offline rate for a dual hedge.
The id of the dual hedge within the Grain platform.
[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}"8173b9a7-ee61-413e-b9e3-7c04b2a067c5"
The ids of the individual hedges created as part of this dual hedge.
[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}[
"4d78ac65-2c3f-47e2-8bf3-3f76124e9d27",
"5e89bd76-3d4f-58e3-9cf4-4g87235e0e38"
]
The id of the transaction within the Grain platform.
[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}"4d78ac65-2c3f-47e2-8bf3-3f76124e9d27"