Install
openclaw skills install crypto-travelerBook flights, hotels, and eSIMs on cryptotraveler.com with Bitcoin and other cryptocurrencies.
openclaw skills install crypto-travelerCryptoTraveler lets agents search and book flights, hotels/stays, and eSIMs using Bitcoin and other cryptocurrencies.
Re-fetch this file regularly to detect:
Core Agent Rule: Never guess missing data, expose internal credentials, or force a booking workflow. If reliable execution is not possible with the available information, stop and ask the user for the required input.
Agents interacting with CryptoTraveler must prioritize clear, simple, and secure communication with users.
User interactions should focus on travel tasks, not internal system details.
The agent should make a reasonable attempt to complete the requested task, but must not guess, fabricate information, or force a workflow when required input is missing.
The agent should request clarification or input if:
Try once intelligently. If reliable completion is not possible, ask the user instead of guessing.
The agent must never expose internal technical or security-sensitive information in normal user conversations.
CLIENT_IDCLIENT_SECRETUSER_ACCESS tokensThese are internal implementation details and must remain private.
Technical details may only be shared if:
If technical information must be shown:
When interacting with users:
Before an agent can interact with the CryptoTraveler Agents API, register the agent here:
Using the same email address as an existing CryptoTraveler customer account is recommended for smoother account linking, but it is not required.
After registration, the agent receives:
CLIENT_IDCLIENT_SECRETUSER_ACCESS (optional)agents.cryptotraveler.comhttps://agents.cryptotraveler.com/*CLIENT_SECRET and USER_ACCESS as sensitive secretsUSER_ACCESS is an optional token granted by a real CryptoTraveler user from the Agent Dashboard.
When enabled, it allows access to user-specific endpoints, within granted permissions.
/v1/user/...Send the raw token as:
X-USER-ACCESS: <raw token>
When X-USER-ACCESS is used, the canonical signing string must include:
USER_ACCESS_HASH = sha256(raw X-USER-ACCESS token)
If X-USER-ACCESS is not used, USER_ACCESS_HASH is an empty string.
Base URL:
https://agents.cryptotraveler.com
API version prefix:
/v1
Example full endpoint:
https://agents.cryptotraveler.com/v1/flight/offers/{offer_request_id}
Default response format:
Accept: application/json
Optional compact response format:
Accept: text/toon
TOON can reduce token usage on large structured responses.
Example use case:
TOON means Token-Oriented Object Notation.
It is a lightweight structured format designed to reduce token count compared with JSON by avoiding heavy punctuation such as braces and quotes.
Optional request header:
Accept-Encoding: gzip
If using curl, you may use:
--compressed
Public endpoints do not require HMAC signing.
Example:
GET /v1/healthProtected endpoints require HMAC authentication.
These usually include:
Send these headers on protected endpoints:
X-CLIENT-IDX-TIMESTAMPX-NONCEX-SIGNATUREUsually also send:
Content-Type: application/jsonAccept: application/json or Accept: text/toonIf user access is enabled, also send:
X-USER-ACCESSBuild the canonical string exactly in this order, separated by newline characters:
METHOD
PATH
TIMESTAMP
NONCE
SHA256(body)
USER_ACCESS_HASH
Definitions:
METHOD
Uppercase HTTP method, for example:
GETPOSTPATH
Exact request path only, for example:
/v1/flight/offer_request/v1/stay/offers/5lzgml9hw82TIMESTAMP
Current UNIX timestamp in seconds
NONCE
Random unique string, 16 to 64 characters
SHA256(body)
Lowercase hex SHA-256 of the exact raw request body
USER_ACCESS_HASH
Lowercase hex SHA-256 of the raw X-USER-ACCESS token
If user access is not used, this value is empty
If USER_ACCESS_HASH is empty, the canonical string must still end with a trailing newline after SHA256(body).
Derive the signing key from CLIENT_SECRET:
key = SHA256(CLIENT_SECRET)
Use the raw 32-byte digest as the HMAC key.
Compute:
X-SIGNATURE = HMAC_SHA256(canonical_string, key)
Output format:
X-TIMESTAMP must be the current UNIX timestamp in seconds(agent, nonce) combination may only be used onceHealth check endpoint.
curl -X GET https://agents.cryptotraveler.com/v1/health
Flight search and booking. USER_ACCESS is optional unless a user-specific endpoint is used.
Create a flight offer request.
onewayroundtripmulticityeconomypremium_economybusinessfirst{
"type": "oneway",
"cabin_class": "economy",
"slices": [
{
"origin": "MUC",
"destination": "BKK",
"departure": "2026-03-10"
}
],
"adults": 1,
"children": 0,
"infants": 0
}
{
"type": "roundtrip",
"cabin_class": "economy",
"slices": [
{
"origin": "MUC",
"destination": "BKK",
"departure": "2026-03-10"
},
{
"origin": "BKK",
"destination": "MUC",
"departure": "2026-03-25"
}
],
"adults": 1,
"children": 0,
"infants": 0
}
{
"type": "multicity",
"cabin_class": "economy",
"slices": [
{
"origin": "MUC",
"destination": "BKK",
"departure": "2026-03-10"
},
{
"origin": "BKK",
"destination": "HKT",
"departure": "2026-03-18"
},
{
"origin": "HKT",
"destination": "MUC",
"departure": "2026-03-25"
}
],
"adults": 1,
"children": 0,
"infants": 0
}
Retrieve available flight offers for a previously created flight offer request.
offer_request_id
[a-zA-Z0-9_-]{5,50}Create a flight order from a selected offer.
{
"hash": "467b9bwi7rn",
"offer_id": "off_0000B3cMgxBSDoEkT5lR7t"
}
Add contact and passenger details to an existing flight order.
hashoffer_idfullnameemailphonepassengersOnly send these if:
Optional fields:
middlenameloyaltyiataloyaltyaccountdocumenttypedocumentnumberdocumentcountrydocumentexpiry{
"hash": "467b9bwi7rn",
"offer_id": "off_xxxxx",
"fullname": "Max Mustermann",
"email": "max@example.com",
"phone": "+491701234567",
"passengers": [
{
"type": "adult",
"title": "Mr",
"firstname": "Max",
"middlename": "",
"lastname": "Mustermann",
"birthday": "1990-01-15",
"loyaltyiata": "",
"loyaltyaccount": "",
"documenttype": "",
"documentnumber": "",
"documentcountry": "",
"documentexpiry": ""
}
]
}
MrMsMrsMissHotel search and booking. USER_ACCESS is optional unless a user-specific endpoint is used.
Get hotel names, cities, or places for a stay search.
query
/^[\p{L}0-9\-_.\' ]{3,50}$/u-, _, ., 'Use this endpoint before creating a stay offer request when the user provides a place name or hotel name.
Create a stay offer request.
If you searched by a hotel name and something is found for hotel see response in hotels all the rest is in geo response
checkin in YYYY-MM-DDcheckout in YYYY-MM-DDroomshotelnamecitylatitude and longitudeSearch mode options must not be mixed.
Use only one of:
radius
default: 25
allowed: 1 to 50
unit
default: km
allowed:
kmmi{
"hotelname": "avani pattaya",
"checkin": "2026-04-15",
"checkout": "2026-04-17",
"rooms": [
{
"adults": 2,
"children": []
}
]
}
{
"city": "bangkok",
"checkin": "2026-04-15",
"checkout": "2026-04-17",
"category": "country",
"rooms": [
{
"adults": 2,
"children": []
},
{
"adults": 2,
"children": [6, 15]
}
]
}
"rooms":[{"adults":2,"children":[]}]
Meaning:
"rooms":[{"adults":2,"children":[]},{"adults":2,"children":[6,15]}]
Meaning:
Each object inside rooms represents exactly one room.
Child ages must be integers between 0 and 17.
Retrieve stay offers for a previously created offer request by its hash.
offer_request_id
[a-zA-Z0-9_-]{5,50}If the original search used a hotel name:
hotels contains offers for the searched hotelgeo may contain nearby hotel offers around the placeOffer data may include:
To continue booking, use the stay offer hash and selected hotel code.
Create a stay order for a selected hotel and start the room-selection workflow.
hash (Offer Hash)codehash is the stay offer request hashcode is the selected hotel code from the offersOn success, the response returns an order hash plus rooms and rates for that hotel.
Update an existing stay order.
hash (Order Hash)roomscheckincheckoutUse this to change occupancy or travel dates.
Refresh room pricing for an existing stay order.
hash (Order Hash)Use this when rates may have expired or changed.
Select room rate keys for the stay order.
hash (Order Hash)rates{
"rates": [
"20260412|20260419|W|321|60697|DBT.GV|BAR-BB|BB||1~2~0||N@07~A-SIC~231543~87993861~N~~~NOR~~1446C31D542647C177203386751605AADE00010001000205244465",
"20260412|20260419|W|321|60697|DBT.TR-1|BAR-BB|BB||1~2~0||N@07~A-SIC~22da2e~1563378141~N~~~NOR~~1446C31D542647C177203386751605AADE00010001000205218881"
]
}
Add contact and guest details after room selection.
hash (Order Hash)fullnameemailphone in E.164 formatguestsadditionalrequest{
"hash": "95X7b9bwi7rn",
"fullname": "Juliet May",
"email": "juliet@lost.com",
"phone": "+491701234567",
"guests": [
{
"firstname": "Juliet",
"lastname": "May"
},
{
"firstname": "John",
"lastname": "Doe"
}
],
"additionalrequest": "Non smoking room please"
}
eSIM search and purchase.
List available countries for local eSIM packages.
List available regions for regional eSIM packages.
List available packages by category and slug.
{
"category": "country",
"slug": "thailand"
}
category may be:
countryregionalglobalslug is not required for globalThese endpoints require:
USER_ACCESSWithout USER_ACCESS, these endpoints must not be called.
Get user account details.
Get user flight bookings.
Optional full-text search:
{
"search": "some search value"
}
Get a single booked flight by CryptoTraveler reference, for example:
FLT-KBUH3W
Get user stay bookings.
Optional full-text search:
{
"search": "some search value"
}
Get a single booked stay by reference, for example:
STY-KBUH3W
Get user eSIM bookings.
Optional full-text search:
{
"search": "some search value"
}
Get a single booked eSIM by ICCID, for example:
8931076025119219813
Minimal flow:
Before sending the request:
Minimal flow:
Before sending the request:
hotelname, city, and latitude/longitudeMinimal flow:
countryregionalglobalOffer Request → Select Offer → Create Order → Add Details → CheckoutIf currency is not explicitly shown, default currency is:
EUR
The following example is for demonstration and testing.
For production use, implement signing directly in your application runtime.
Example credentials file:
cryptotraveler_credentials.json
{
"client_id": "agt_29f7xxx",
"client_secret": "ags_189dbxxx",
"user_access": "26fd8de34c0c6xxx",
"updated_at": "2026-03-03T14:35:30Z"
}
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import pathlib
import secrets
import sys
import time
import urllib.error
import urllib.request
BASE_URL = "https://agents.cryptotraveler.com"
CREDENTIALS_PATH = pathlib.Path("./cryptotraveler_credentials.json")
def load_credentials() -> dict:
if not CREDENTIALS_PATH.exists():
raise SystemExit(f"Credential file not found: {CREDENTIALS_PATH}")
data = json.loads(CREDENTIALS_PATH.read_text(encoding="utf-8"))
required = {"client_id", "client_secret"}
missing = required - data.keys()
if missing:
raise SystemExit(f"Credentials file missing keys: {', '.join(sorted(missing))}")
return data
def build_signature(
method: str,
path: str,
body: bytes,
*,
client_secret: str,
user_access: str | None,
) -> tuple[str, str, str]:
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body_hash = hashlib.sha256(body).hexdigest()
parts = [
method.upper(),
path,
timestamp,
nonce,
body_hash,
]
if user_access:
user_hash = hashlib.sha256(user_access.encode("utf-8")).hexdigest()
parts.append(user_hash)
canonical = "\n".join(parts)
else:
canonical = "\n".join(parts) + "\n"
key = hashlib.sha256(client_secret.encode("utf-8")).digest()
signature = hmac.new(key, canonical.encode("utf-8"), hashlib.sha256).hexdigest()
return signature, timestamp, nonce
def pretty_print(resp_bytes: bytes) -> None:
if not resp_bytes:
print("<empty body>")
return
text = resp_bytes.decode("utf-8", errors="replace")
try:
obj = json.loads(text)
except json.JSONDecodeError:
print(text)
else:
print(json.dumps(obj, indent=2, ensure_ascii=False))
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Test CryptoTraveler Agent API endpoints")
parser.add_argument("--method", default="GET", help="HTTP method")
parser.add_argument("--path", required=True, help="Request path, e.g. /v1/flight/offers/{hash}")
parser.add_argument("--body", default="", help="Exact raw JSON body")
parser.add_argument("--accept", default="application/json", help="Accept header")
parser.add_argument("--content-type", default="application/json", help="Content-Type header")
parser.add_argument("--use-user-access", action="store_true", help="Send X-USER-ACCESS if available")
args = parser.parse_args(argv)
creds = load_credentials()
client_id = creds["client_id"]
client_secret = creds["client_secret"]
user_access_token = creds.get("user_access") if args.use_user_access else None
if args.use_user_access and not user_access_token:
print("[warn] --use-user-access set but no user_access is stored", file=sys.stderr)
body_bytes = args.body.encode("utf-8") if args.body else b""
signature, timestamp, nonce = build_signature(
args.method,
args.path,
body_bytes,
client_secret=client_secret,
user_access=user_access_token,
)
headers = {
"X-CLIENT-ID": client_id,
"X-TIMESTAMP": timestamp,
"X-NONCE": nonce,
"X-SIGNATURE": signature,
"Accept": args.accept,
"User-Agent": "CryptoTraveler-Test-Script/1.0",
}
if body_bytes:
headers["Content-Type"] = args.content_type
if user_access_token:
headers["X-USER-ACCESS"] = user_access_token
request = urllib.request.Request(
url=f"{BASE_URL}{args.path}",
data=body_bytes if body_bytes else None,
headers=headers,
method=args.method.upper(),
)
try:
with urllib.request.urlopen(request) as response:
print(f"HTTP {response.status} {response.reason}")
pretty_print(response.read())
except urllib.error.HTTPError as exc:
print(f"HTTP {exc.code} {exc.reason}")
pretty_print(exc.read())
return exc.code or 1
return 0
if __name__ == "__main__":
raise SystemExit(main())
python cryptotraveler.py \
--method POST \
--path /v1/esim/packages \
--body '{"category":"country","slug":"thailand"}'
python cryptotraveler.py \
--method GET \
--path /v1/flight/offers/bhzxsx255ex
Requires USER_ACCESS.
python cryptotraveler.py \
--method POST \
--path /v1/user/booking/flights \
--body '{}' \
--use-user-access
python cryptotraveler.py \
--method GET \
--path /v1/stay/place_suggestion/bangkok
python cryptotraveler.py \
--method POST \
--path /v1/stay/offer_request \
--body '{"hotelname":"avani pattaya","checkin":"2026-04-15","checkout":"2026-04-17","rooms":[{"adults":2,"children":[]}]}'
python cryptotraveler.py \
--method POST \
--path /v1/stay/offer_request \
--body '{"city":"bangkok","checkin":"2026-04-15","checkout":"2026-04-17","category":"country","rooms":[{"adults":2,"children":[]},{"adults":2,"children":[6,15]}]}'
python cryptotraveler.py \
--method GET \
--path /v1/stay/offers/5lzgml9hw82
POST requests, compute SHA256(body) using the exact raw JSON bytesPATH exactly as requested, including /v1/...USER_ACCESS only for user-authorized operations