This page is currently available in Persian only.
Hamta Partner Rates API
With this API your website, online store or bot receives your own partner account's rates directly from Hamta: no manual price entry, always in sync with the partner portal.
Overview
The Hamta API is a read-only REST service that returns JSON encoded as UTF-8. Each API key belongs to one partner account and returns that account's rates only: your cost from Hamta, and your customer price with the margin you set yourself in the partner portal. Reference market rates are not provided by this API.
https://hamtagift.com
All requests must be sent over HTTPS.
Authentication
Every request must send the API key in the Authorization header as a Bearer token. Keys start with the prefix hamta_live_.
- Sign in to the partner portal and open the “API & Telegram” page.
- Create a new key (the Owner, Manager and Developer roles can create keys).
- The key is shown only once: store it in a secure environment variable on your server. Hamta keeps only a hashed copy; if you lose the key, revoke it and create a new one.
Authorization: Bearer hamta_live_…Never put the API key in browser-side code (page JavaScript), a mobile app or a public code repository. Send requests from your server only.
Get rates
Returns the current rates of every instrument on your partner account. The request takes no parameters or body, and the response is never cached (Cache-Control: no-store).
Hours and access
The rates API answers on market days from 11:00 to 20:00 Tehran time (the last accepted request is at 19:59). It is closed on Fridays and official market holidays. Outside these hours it returns 503 with the code OUTSIDE_MARKET_HOURS; the Retry-After header gives the seconds until the next opening, and the next_open field gives its time (ISO 8601, UTC).
{
"ok": false,
"success": false,
"error": "OUTSIDE_MARKET_HOURS",
"message": "<string>",
"market_hours": "11:00 - 20:00 Asia/Tehran",
"next_open": "<ISO 8601 string>"
}The IP allowlist is optional and is set up by Hamta support at your request. As long as no address is registered, your keys are accepted from any IP. Once one or more addresses are registered, every request from any other address is refused with 403 and the code IP_NOT_ALLOWED. Only exact public IPv4 or IPv6 addresses are accepted (no ranges), at most 10; the list applies to every key of your account and is shown in the partner portal (API & Telegram section).
{
"ok": false,
"success": false,
"error": "IP_NOT_ALLOWED",
"message": "<string>"
}Response
A successful response has status 200. The structure below contains exactly the keys the API returns, with the data type written in place of each numeric value. To see the real response for your own account, open the “API & Telegram” page in the partner portal.
{
"ok": true,
"partner": "<string>",
"updatedAt": "<ISO 8601 string | null>",
"note": "buy = customer buys (خرید از ما), sell = customer sells (فروش به ما). null = not configured.",
"rates": [
{
"code": "havaleLir",
"label": "لیر حواله ترکیه",
"unit": "TOMAN",
"unitBasis": 1,
"partnerCost": {
"retail": {
"buy": "<decimal string | null>",
"sell": "<decimal string | null>"
},
"wholesale": {
"buy": "<decimal string | null>",
"sell": "<decimal string | null>"
}
},
"customerPrice": {
"retail": {
"buy": "<decimal string | null>",
"sell": "<decimal string | null>"
},
"wholesale": {
"buy": "<decimal string | null>",
"sell": "<decimal string | null>"
}
}
},
…
]
}Fields
| Field | Type | Description |
|---|---|---|
ok | boolean | Always true in a successful response and false in an error response. |
partner | string | Your company name as registered on the partner account. |
updatedAt | string | null | When Hamta's reference rates were last updated, in ISO 8601 (UTC); null if unknown. |
note | string | A fixed note on the meaning of buy, sell and null. |
rates | array | The list of instruments, always in this order. |
rates[].code | string | Stable instrument identifier; rely on this field in your code, not on label. |
rates[].label | string | Persian display name of the instrument; it may change. |
rates[].unit | "TOMAN" | "TRY" | Price unit: TOMAN is Iranian toman, TRY is Turkish lira. |
rates[].unitBasis | integer | The price is for this many units; for example 1000 means the price of 1,000 units. |
rates[].partnerCost | { retail, wholesale } | Your cost from Hamta, split into retail and wholesale tiers. |
rates[].customerPrice | { retail, wholesale } | Your customer price: your cost plus the margin you set in the partner portal (1% by default). During a discount campaign that margin can go down to -2%, so the customer price can be better than your cost. null if no margin is set for that direction. |
….buy | string | null | The price at which your customer buys from you. |
….sell | string | null | The price at which your customer sells to you. |
Every price is a decimal string (up to 6 decimal places) or null. null means “not configured”; never treat it as zero.
Instruments
The rates list always contains these instruments, in this order:
| code | Instrument | unit | unitBasis |
|---|---|---|---|
havaleLir | Turkish lira transfer (toman per lira) | TOMAN | 1 |
dollar | US dollar (toman per dollar) | TOMAN | 1 |
euro | Euro (toman per euro) | TOMAN | 1 |
usdtToLir | Turkish lira per USDT | TRY | 1 |
AMD_1000 | Armenian dram (toman per 1,000 dram) | TOMAN | 1000 |
usdtPrice | Toman per USDT | TOMAN | 1 |
AFN | Afghan afghani (toman per afghani) | TOMAN | 1 |
onlineShop | Online-shop lira (toman per lira) | TOMAN | 1 |
sipay | HamtaCard lira (toman per lira) | TOMAN | 1 |
Errors
API errors are returned as JSON with "ok": false and a code in "error". IP_NOT_ALLOWED and OUTSIDE_MARKET_HOURS also carry "success": false and a "message" in the language of your Accept-Language header (fa, en, tr, hy; English otherwise). The last two rows come from the site's security layer and are plain text, not JSON.
| Status | error | Meaning | What to do |
|---|---|---|---|
401 | missing_or_malformed_token | The Authorization header is missing or the key does not have the hamta_live_ format. | Send the header as Bearer with the complete key. |
401 | invalid_token | The key is unknown or revoked, or the partner account is not active. | Create a new key in the partner portal or contact Hamta. |
403 | IP_NOT_ALLOWED | The key is valid, but the request did not come from an address on your account's IP allowlist. | Send from a registered address, or contact Hamta support to change the list. |
429 | rate_limited | More than 60 requests in one minute with this key. | Wait for the Retry-After header (seconds) and cache the result. |
503 | OUTSIDE_MARKET_HOURS | Outside market hours (11:00–20:00 Tehran time) or on a market holiday. | Wait for Retry-After or next_open and keep showing your last valid rates. |
503 | rates_unavailable | Rates are temporarily unavailable. | Keep your last valid rates and try again later. |
403 | — | The request was refused by the site's security rules (plain-text Forbidden). | If your request is ordinary, contact Hamta support. |
429 | — | Too many requests from one IP address (plain text). | Wait for Retry-After and space your requests further apart. |
Limits
- At most 60 requests per minute per API key.
- Requests are answered only on market days, 11:00–20:00 Tehran time (see Hours and access).
- Requests refused because of the IP allowlist do not count against your key's per-minute limit.
- In addition, each IP address has a separate site-wide request limit (sustained at most about 2 requests per second).
- Only the GET method is supported.
Ready-to-use code samples
The samples below read the key from the HAMTA_API_KEY environment variable, check for errors and print each instrument's retail customer price. The PHP sample suits online stores such as WooCommerce or Laravel.
<?php
// Server side only: never send the API key to the browser.
$apiKey = getenv('HAMTA_API_KEY'); // hamta_live_...
$ch = curl_init('https://hamtagift.com/api/partner/v1/rates');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Accept: application/json'],
CURLOPT_TIMEOUT => 10,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($body === false) {
throw new RuntimeException('Hamta API unreachable: ' . $error);
}
$data = json_decode($body, true); // null for the plain-text 403/429 of the security layer
if ($status !== 200 || !is_array($data) || empty($data['ok'])) {
// 401: check the key. 429: wait for Retry-After. 503: keep your last good prices.
throw new RuntimeException('Hamta API error: ' . ($data['error'] ?? ('HTTP ' . $status)));
}
foreach ($data['rates'] as $rate) {
// Prices are decimal strings or null (= not configured). Keep them as strings
// (e.g. bcmath) rather than float for money.
$sellToCustomer = $rate['customerPrice']['retail']['buy'];
echo $rate['code'], ': ', $sellToCustomer ?? 'not configured', PHP_EOL;
}import os
from decimal import Decimal
import requests
API_KEY = os.environ["HAMTA_API_KEY"] # hamta_live_...
resp = requests.get(
"https://hamtagift.com/api/partner/v1/rates",
headers={"Authorization": f"Bearer {API_KEY}", "Accept": "application/json"},
timeout=10,
)
try:
data = resp.json()
except ValueError: # plain-text 403/429 from the security layer
data = None
if resp.status_code != 200 or not data or not data.get("ok"):
# 401: check the key. 429: wait for Retry-After. 503: keep your last good prices.
raise RuntimeError(f"Hamta API error: {(data or {}).get('error', resp.status_code)}")
for rate in data["rates"]:
price = rate["customerPrice"]["retail"]["buy"] # decimal string or None
print(rate["code"], Decimal(price) if price is not None else "not configured")// Node.js 18+, ES module (.mjs). Server side only.
const API_KEY = process.env.HAMTA_API_KEY; // hamta_live_...
const res = await fetch("https://hamtagift.com/api/partner/v1/rates", {
headers: { Authorization: `Bearer ${API_KEY}`, Accept: "application/json" },
signal: AbortSignal.timeout(10_000),
});
const data = await res.json().catch(() => null); // null for the plain-text 403/429
if (!res.ok || !data?.ok) {
// 401: check the key. 429: wait for Retry-After. 503: keep your last good prices.
throw new Error(`Hamta API error: ${data?.error ?? `HTTP ${res.status}`}`);
}
for (const rate of data.rates) {
// Decimal strings or null; parse with a decimal library, not Number, for money.
console.log(rate.code, rate.customerPrice.retail.buy ?? "not configured");
}# HAMTA_API_KEY holds your key (hamta_live_...)
curl -sS "https://hamtagift.com/api/partner/v1/rates" \
-H "Authorization: Bearer $HAMTA_API_KEY" \
-H "Accept: application/json"Best practices
- Cache the result on your server for at least one minute rather than sending a request for every page view.
- Handle prices as strings or an exact decimal type (Decimal, bcmath), not as floating-point numbers.
- Look instruments up by code; label is for display only.
- Show null as “not configured” and never substitute another number for it.
- If a request fails, keep your last valid rates together with their updatedAt time.
- Create a separate key for each service or environment, so you can revoke just that one if needed.
Ready to connect?
You create API keys in the partner portal. If you are not yet a Hamta partner, contact us to open a partner account.