curl --request POST \
--url https://api.arcus.xyz/v1/affiliate/redeemInvite \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--header 'X-Signature: <api-key>' \
--header 'X-Timestamp: <api-key>' \
--data '
{
"address": "<string>",
"inviteCode": "<string>"
}
'import requests
url = "https://api.arcus.xyz/v1/affiliate/redeemInvite"
payload = {
"address": "<string>",
"inviteCode": "<string>"
}
headers = {
"X-API-Key": "<api-key>",
"X-Timestamp": "<api-key>",
"X-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-Timestamp': '<api-key>',
'X-Signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({address: '<string>', inviteCode: '<string>'})
};
fetch('https://api.arcus.xyz/v1/affiliate/redeemInvite', 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.arcus.xyz/v1/affiliate/redeemInvite",
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([
'address' => '<string>',
'inviteCode' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-Signature: <api-key>",
"X-Timestamp: <api-key>"
],
]);
$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.arcus.xyz/v1/affiliate/redeemInvite"
payload := strings.NewReader("{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-Timestamp", "<api-key>")
req.Header.Add("X-Signature", "<api-key>")
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.arcus.xyz/v1/affiliate/redeemInvite")
.header("X-API-Key", "<api-key>")
.header("X-Timestamp", "<api-key>")
.header("X-Signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arcus.xyz/v1/affiliate/redeemInvite")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-Timestamp"] = '<api-key>'
request["X-Signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": "submitted"
}{
"status": "submitted"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "rate limited",
"reason": "account_empty",
"retryAfterMs": 850,
"clientId": "my-order-42"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}Redeem an invite code
Redeems a single-use invite code.
curl --request POST \
--url https://api.arcus.xyz/v1/affiliate/redeemInvite \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--header 'X-Signature: <api-key>' \
--header 'X-Timestamp: <api-key>' \
--data '
{
"address": "<string>",
"inviteCode": "<string>"
}
'import requests
url = "https://api.arcus.xyz/v1/affiliate/redeemInvite"
payload = {
"address": "<string>",
"inviteCode": "<string>"
}
headers = {
"X-API-Key": "<api-key>",
"X-Timestamp": "<api-key>",
"X-Signature": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
'X-API-Key': '<api-key>',
'X-Timestamp': '<api-key>',
'X-Signature': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({address: '<string>', inviteCode: '<string>'})
};
fetch('https://api.arcus.xyz/v1/affiliate/redeemInvite', 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.arcus.xyz/v1/affiliate/redeemInvite",
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([
'address' => '<string>',
'inviteCode' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>",
"X-Signature: <api-key>",
"X-Timestamp: <api-key>"
],
]);
$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.arcus.xyz/v1/affiliate/redeemInvite"
payload := strings.NewReader("{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("X-Timestamp", "<api-key>")
req.Header.Add("X-Signature", "<api-key>")
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.arcus.xyz/v1/affiliate/redeemInvite")
.header("X-API-Key", "<api-key>")
.header("X-Timestamp", "<api-key>")
.header("X-Signature", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arcus.xyz/v1/affiliate/redeemInvite")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["X-Timestamp"] = '<api-key>'
request["X-Signature"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"address\": \"<string>\",\n \"inviteCode\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"status": "submitted"
}{
"status": "submitted"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "rate limited",
"reason": "account_empty",
"retryAfterMs": 850,
"clientId": "my-order-42"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}{
"error": "Invalid request body",
"code": "GEO_RESTRICTED",
"errorSource": "Order",
"errorType": "Tick",
"rejectionReason": "POST_ONLY_WOULD_CROSS"
}- bound as a referee of the invite code’s owner (same effect as
POST /v1/affiliate/registerAffiliateunder the owner’s referral code), and - granted perps trading access (a second avenue beside the static API access whitelist; effective within the grant propagation interval, typically under 30 seconds).
409.
The response does not name the code’s owner. The redeemer never learns the address of the trader who invited them; GET /v1/affiliate/myReferrer redacts the same address.
Requirements: the code’s owner must currently hold a referral code, self-redemption is rejected, and the standard registration volume gate applies.
An already-referred caller may be reassigned — but only when this code is what grants them access. Invite codes are earned by their owner, so the owner takes the referral when redeeming is what actually opens the door; a code that grants nothing may not take another affiliate’s referee:
| Caller can already trade | Caller already referred | Outcome |
|---|---|---|
| no | no | bound to the code’s owner |
| no | yes | attribution moved to the code’s owner |
| yes | no | bound to the code’s owner |
| yes | yes | 409 — nothing to change, and the code is not spent |
409 — it would spend a code without changing anything.
A move is confirmed asynchronously and has no firehose ack, so that path always answers 202 rather than 200.
Retry-safe: every step is idempotent. If the response is a 502 (”… retry to complete”), the code is claimed by the caller and retrying the identical request completes the redemption.
Guessing guard. Invite codes are short, so this endpoint enforces a tight budget on attempts that name a code which does not exist, on top of the standard rate limit. A caller who submits several unknown codes in quick succession receives 429 with Retry-After; the budget is not consumed by successful redemptions.Authorizations
Hex-encoded Ed25519 public key (64 chars). The public key IS the API key — register it via POST /createApiKey. Required on every authenticated request, both read-only and signed.
Unix time in nanoseconds as a decimal string (e.g. "1713825891591000000"). Millisecond or second epochs are rejected with 401 Unauthorized. Must be within ±30,000 ms of server wall-clock, or the request is rejected with 401 Unauthorized. Required on all mutating / credential-creating endpoints. This same value must appear as the ct field in the ordersign typed canonical payload (single-order endpoints) or in each element's ct field (batch endpoints).
Lowercase hex-encoded Ed25519 signature (128 chars).
Single-order endpoints (placeOrder, cancelOrder, modifyOrder, and other non-batch mutating routes) sign over the ordersign typed canonical payload — a compact, key-sorted JSON object built from parsed request fields using engine-native integer values:
placeOrder: {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,"m":N,"op":1,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1}
cancelOrder: {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,[,"id":"…"],"m":N,"op":2,"v":1}
modifyOrder: {"ad":"0x…","ai":N,[,"c":"…"],"ct":N,"g":N,[,"id":"…"],"m":N,"op":3,"p":N,"q":N,"r":0|1,"s":N,"t":N,"v":1} (exactly one of id / c)
ct must equal the X-Timestamp header value. Keys in brackets are conditional (omitted when empty). op values: 1=place, 2=cancel, 3=modify. See the ordersign package for field definitions and reference signing code.
Other signed routes (e.g. createApiKey) still use the legacy scheme: signing_message = X-Timestamp + ACTION + canonicalJSON(body), where ACTION is the camelCase final path segment.
Batch endpoints (batchPlaceOrders, batchCancelOrders, batchModifyOrders) do NOT use this header. They authenticate with per-element typed ordersign signatures embedded in the request body (see the global auth description and the per-field signature descriptions on OrderRequest / CancelOrderRequest / ModifyOrderRequest).
Read endpoints are authenticated by ?address= (and optionally X-API-Key) only — no signature is required, except for the signed affiliate reads (see the Referral tag), which require the full header triple; with no body their signing message is X-Timestamp + ACTION. canonicalJSON(body) is the JSON body with object keys sorted lexicographically at every level and no whitespace; the server canonicalizes the received body before verifying, so only the bytes signed over must be canonical. Required on all mutating / credential-creating endpoints.
Body
Ethereum address of the redeemer (must match signer).
Invite code to redeem. The canonical form is 6 uppercase base36 characters (A-Z, 0-9); any casing is accepted, and spaces and dashes are stripped so a code copied from a chat message works as sent.
1 - 32Response
Redemption confirmed by the matching engine (referee binding ack received within the server-side timeout). Trading access follows within the grant propagation interval.
Deliberately does not identify the invite code's owner. Signing the request proves who the caller is, which is no obstacle to redeeming a code purely to learn who owns it, so echoing the owner back made redemption a one-shot referral-code → wallet lookup. Poll GET /v1/affiliate/myReferrer for the binding's code and discount.
submitted Was this page helpful?