Skip to main content
POST
/
v1
/
createApiKey
Create API key
curl --request POST \
  --url https://api.testnet.arcus.xyz/v1/createApiKey \
  --header 'Content-Type: application/json' \
  --data '
{
  "address": "<string>",
  "publicKey": "<string>",
  "apiWalletName": "<string>",
  "signature": {
    "r": "<string>",
    "s": "<string>",
    "v": "<string>"
  },
  "validUntil": 2
}
'
{
  "apiKey": "<string>",
  "address": "<string>",
  "createdAt": 123,
  "accountIndex": 4,
  "validUntil": 123
}
Create an API key for the ethereum address. No address or account parameter is required; all keys are associated with the master account.

Signing the request

The request is authenticated by an EIP-191 personal_sign signature produced by the wallet that owns address. Frontend code should use the standard wallet API (MetaMask, ethers, viem, …) — the wallet handles the EIP-191 prefix automatically; you do not need to construct it yourself. Build the canonical message and call personal_sign:
// viem
const message = JSON.stringify({
  apiWalletName: "Arcus",
  apiWalletPublicKey: pubKeyHex,   // 64 hex chars, no 0x
  validUntil,                      // epoch ms
});
const sig = await walletClient.signMessage({ account, message });

// ethers
const sig = await signer.signMessage(message);
The wallet returns a 65-byte signature 0x<r:32><s:32><v:1>. Split it into the three hex components for the signature field of the request body:
const signature = {
  r: "0x" + sig.slice(2, 66),
  s: "0x" + sig.slice(66, 130),
  v: "0x" + sig.slice(130, 132),
};

Canonical message format

The string passed to personal_sign MUST be JSON with keys in this exact order, no whitespace, no trailing newline:
{"apiWalletName":"<apiWalletName>","apiWalletPublicKey":"<publicKey>","validUntil":<validUntil>}
The gateway rebuilds this same string from the request body fields, applies the EIP-191 prefix on its side, recovers the signer with ecrecover, and rejects the request with HTTP 401 if the recovered address does not equal address. Any deviation in whitespace, key order, encoding, or types will produce a different hash and fail the recovery check.

Body

application/json
address
string
required

Ethereum address to associate with the account.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
Example:

"0x742d35cc6634c0532925a3b844bc9e7595f2bd18"

publicKey
string
required

Hex-encoded Ed25519 public key. Becomes the API key.

Required string length: 64
Pattern: ^[0-9a-fA-F]{64}$
Example:

"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"

apiWalletName
string
required

Name of the API wallet (included in the signing message).

Required string length: 1 - 64
Example:

"Arcus"

signature
object
required

EIP-191 personal_sign signature (r, s, v) produced by the wallet that owns address. See the POST /v1/createApiKey endpoint description for the full client-side signing recipe (canonical message, wallet API examples, splitting (r, s, v)). Requests where the recovered signer does not equal address are rejected with HTTP 401.

validUntil
integer<int64>

Expiration timestamp (epoch ms). Must be between 1 day and 180 days from the server's current time (inclusive). If omitted, defaults to 14 days from now. Explicit values outside the [now+1d, now+180d] window are rejected with HTTP 400.

Required range: x >= 1

Response

API key creation accepted. The request has been sent to go-core / persistence but the key is not yet usable for authenticated requests; the caller must wait briefly for persistence to ingest the APIKeyOperation before the returned api_key resolves.

apiKey
string
required

Newly generated API key (hex string).

address
string
required

20-byte EVM address as hex: optional 0x or 0X prefix and exactly 40 hexadecimal digits. API responses normalize to lowercase af after 0x.

Pattern: ^(0x|0X)?[0-9a-fA-F]{40}$
createdAt
integer<int64>
required

Creation timestamp (epoch microseconds).

accountIndex
integer

Account index for this API key (optional; omit when not applicable).

Required range: 0 <= x <= 9
validUntil
integer<int64>

Expiration timestamp (epoch ms). Echoes the client-supplied expiry, so it stays in milliseconds. Absent if no expiry.