Skip to main content

API Agents

API agents let a wallet owner create durable bot credentials without sending the wallet private key to Senticore. The owner signs an authorization message with the wallet, and the API returns an agent plus optional machine credentials.

Use this flow for server-side bots, BSL quote engines, FIX sessions, market maker tooling, and integration tests that should not ask MetaMask to sign every order.

Choose the agent type deliberately:

Agent typeUse
api_agentStandard server-side API bots, private reads, and delegated HTTP workflows
institutional_agentBSL/FIX business-line access, quote engines, market-maker tooling, drop-copy, and FIX Logon

Runtime-session agents and durable machine agents have different lifetime ceilings. Keep frontend sessions below their caps and use durable agents for server processes:

Agent typeMaximum lifetimeCredential support
browser_session30 daysNo HMAC/Ed25519 credential; wallet-created runtime signer
mobile_session24 hoursNo HMAC/Ed25519 credential; mobile handoff signer
api_agent180 daysHMAC or Ed25519 HTTP machine auth
institutional_agent90 daysHMAC for BSL/FIX/FIXP and protected institutional reads

The browser trading UI intentionally requests slightly less than 30 days to leave clock-skew margin. Do not request exactly the cap from clients whose clock may be ahead of the server.

In the current private beta, FIX order-entry Logon and BSL order-entry machine auth require an HMAC credential on an institutional_agent. A normal api_agent HMAC credential is useful for HTTP trading bots and private reads, but it is rejected for BSL/FIX order entry. Create institutional_agent credentials for every BSL or FIX integration.

After creating the institutional_agent, read the self-service connectivity bundle:

GET /api/v1/bsl/connectivity
GET /api/v1/fix/connectivity

With institutional_agent HMAC headers, the bundle returns the recommended FIX SenderCompID(49), TargetCompID(56), direct FIX host/port, and direct BSL HTTP base URL. Market makers should not need a manual operations ticket just to choose FIX CompIDs.

Credential types

Credential typeReturned secretsUse
HMACapiKeyId, apiSecret, apiPassphraseFIX Logon, SC-Auth-Version: 2, durable bot/API access
Ed25519apiKeyId, public key onlyHTTP machine auth where Ed25519 is enabled

For FIX, request HMAC credentials. Ed25519 credentials are HTTP-only and are not accepted by the current FIX Logon path.

One-call HMAC agent creation

POST /api/agents/create can create the agent and issue the HMAC credential in the same response. Set issueInitialCredential to true.

POST /api/agents/create
Content-Type: application/json
{
"authorization": {
"domain": "app.sentico-labs.xyz",
"appName": "SentiPredict",
"scheme": "senticore_typed_v2",
"environment": "Arbitrum One",
"chainId": 42161,
"userWalletAddress": "0xOwnerWallet",
"accountIdHex": "0xOptional32ByteTradingAccountId",
"agentPublicKey": "0xBusinessLineAgentAddress",
"label": "BSL quote engine",
"agentType": "institutional_agent",
"scopes": ["read", "trade", "cancel", "quote", "drop_copy"],
"policy": {},
"expiresAt": 1790000000000,
"authorizationNonce": "7c4a0e4a5d43c4d8c9e2a55b",
"issuedAt": 1760000000000,
"signature": "0x..."
},
"issueInitialCredential": true
}
Scope semantics

quote is limited to quote-native RFQ and institutional MM surfaces. Ordinary REST batch place/amend items require trade; batch cancels require cancel. A quote-only credential therefore cannot place, amend, or cancel an ordinary order through /api/v1/trading/orders/batch.

Successful responses include the agent, the credential id, and the HMAC secret material. Store apiSecret and apiPassphrase immediately; they are shown only once.

{
"ok": true,
"agent": {
"id": "agt_...",
"agentType": "institutional_agent",
"scopes": ["read", "trade", "cancel", "quote", "drop_copy"]
},
"credential": {
"apiKeyId": "spk_...",
"authScheme": "hmac_v2"
},
"apiSecret": "sps_...",
"apiPassphrase": "spp_...",
"warning": "Legacy HMAC secret and passphrase are shown only once..."
}

Authorization message

The wallet signs the exact text below with the owner wallet. Browser wallets use the normal message signing flow, for example MetaMask personal_sign.

SentiCore Typed Agent Authorization
type:senticore.agent_authorization
version:2
app:SentiPredict
domain:app.sentico-labs.xyz
environment:Arbitrum One
chainId:42161
wallet:0xownerwallet
agentPublicKey:0xbusinesslineagentaddress
label:BSL quote engine
agentType:institutional_agent
scopes:read,trade,cancel,quote,drop_copy
policy:{}
nonce:7c4a0e4a5d43c4d8c9e2a55b
issuedAt:1760000000000
expiresAt:1790000000000

If the authorization request is bound to a specific trading account, send authorization.accountIdHex in the JSON request. It is the internal 32-byte trading-account id returned by the trading-account control plane. The signed message line remains accountId: for compatibility with the typed v2 message:

accountId:0x...

accountIdHex is not the 20-byte owner wallet address and it is not the 20-byte engine account used in signed order payloads. Most external integrations should omit accountIdHex on first setup; the API derives the wallet's main trading account. Putting the wallet address into accountIdHex returns bad accountId: expected 32 bytes.

For the default policy, sign exactly policy:{} as shown above. For a custom policy, do not hand-roll JSON key order. Use the SDK helper or mirror the server canonical policy object exactly (allowedMarkets, allowedAccounts, ipAllowlist, maxOrderNotionalMicro, rateLimitPerMinute, institutionalProfile) before signing.

appName, domain, environment, and chainId must match the deployed runtime configuration. In the current beta frontend the app name is SentiPredict.

TypeScript example

This example uses an owner wallet to authorize a newly generated BSL/FIX agent address. It requests HMAC credentials by setting issueInitialCredential: true.

import { BrowserProvider, Wallet, hexlify, randomBytes } from "ethers";

const API_BASE_URL = "https://api.sentico-labs.xyz";
const owner = "0xOwnerWallet";
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();

const now = Date.now();
const institutionalLifetimeMs = 90 * 24 * 60 * 60 * 1000 - 5 * 60 * 1000;
const agent = Wallet.createRandom();
const authorization = {
domain: "app.sentico-labs.xyz",
appName: "SentiPredict",
scheme: "senticore_typed_v2",
environment: "Arbitrum One",
chainId: 42161,
userWalletAddress: owner.toLowerCase(),
agentPublicKey: agent.address.toLowerCase(),
label: "BSL quote engine",
agentType: "institutional_agent",
scopes: ["read", "trade", "cancel", "quote", "drop_copy"],
policy: {},
expiresAt: now + institutionalLifetimeMs,
authorizationNonce: hexlify(randomBytes(12)).slice(2),
issuedAt: now
};

const message = [
"SentiCore Typed Agent Authorization",
"type:senticore.agent_authorization",
"version:2",
`app:${authorization.appName}`,
`domain:${authorization.domain}`,
`environment:${authorization.environment}`,
`chainId:${authorization.chainId}`,
`wallet:${authorization.userWalletAddress}`,
`agentPublicKey:${authorization.agentPublicKey}`,
`label:${authorization.label}`,
`agentType:${authorization.agentType}`,
`scopes:${authorization.scopes.join(",")}`,
`policy:${JSON.stringify(authorization.policy)}`,
`nonce:${authorization.authorizationNonce}`,
`issuedAt:${authorization.issuedAt}`,
`expiresAt:${authorization.expiresAt}`
].join("\n");

const signature = await signer.signMessage(message);

const response = await fetch(`${API_BASE_URL}/api/agents/create`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
authorization: { ...authorization, signature },
issueInitialCredential: true
})
});

if (!response.ok) {
throw new Error(await response.text());
}

const created = await response.json();
console.log({
agentId: created.agent.id,
apiKeyId: created.credential.apiKeyId,
apiSecret: created.apiSecret,
apiPassphrase: created.apiPassphrase,
agentPrivateKey: agent.privateKey
});

The generated agentPrivateKey belongs to the agent, not to the owner wallet. Keep it with the bot if the bot will sign delegated trading actions. Never export or upload the owner wallet private key.

Two-step credential creation

If the agent already exists, create or rotate a credential separately:

POST /api/agents/{agentId}/credentials/create
Content-Type: application/json
{
"walletAuth": {
"domain": "app.sentico-labs.xyz",
"appName": "SentiPredict",
"scheme": "senticore_typed_v2",
"environment": "Arbitrum One",
"chainId": 42161,
"walletAddress": "0xOwnerWallet",
"nonce": "0f2a9d64a2f40efb6e0f567c",
"issuedAt": 1760000000000,
"expiresAt": 1760000300000,
"signature": "0x..."
},
"credentialKind": "hmac",
"cancelOnDisconnect": true,
"defaultStpMode": "cancel_taker"
}

The wallet signs this admin message:

SentiCore Typed Wallet Admin Authorization
type:senticore.wallet_admin
version:2
app:SentiPredict
domain:app.sentico-labs.xyz
environment:Arbitrum One
chainId:42161
wallet:0xownerwallet
action:create_agent_credential
targetAgentId:agt_...
nonce:0f2a9d64a2f40efb6e0f567c
issuedAt:1760000000000
expiresAt:1760000300000

To create an Ed25519 credential instead, send credentialKind: "ed25519" plus publicKeyAlgorithm: "ed25519" and publicKey. Do not use Ed25519 for FIX.

Using HMAC credentials

For HTTP machine auth:

SC-Auth-Version: 2
SC-Key: <apiKeyId>
SC-Nonce: <monotonic nonce>
SC-Timestamp: <unix ms>
SC-Passphrase: <apiPassphrase>
SC-Signature: <hmac signature>

Always send SC-Auth-Version: 2 for HMAC v2. If the header is omitted, the server treats the request as legacy v1 and signs a different canonical string.

For FIX Logon, use an HMAC credential issued to the business-line institutional_agent:

FIX tagValue
553apiKeyId
554apiSecret:apiPassphrase
49Bundle fix.orderEntry.senderCompIdTag49 for order entry, or fix.dropCopy.senderCompIdTag49 for drop-copy
56Bundle fix.targetCompIdTag56, normally SENTICORE
57order_entry or drop_copy

The private-beta FIX listener is raw TCP/TLS, not an HTTPS route. Current live operations exposes direct TCP/TLS on fix.sentico-labs.xyz:9878 with TLS SNI fix.sentico-labs.xyz. A Cloudflare-proxied api.sentico-labs.xyz:9878 connection will not work unless operations configures a TCP proxy or DNS-only record.

For BSL order-entry over HTTP, institutional_agent HMAC headers can authorize the account-scoped business-line path without a shared lane key. A separate order-entry entitlement may still be required for dedicated capacity or a contractual rate tier:

X-Senticore-Order-Entry-Key: <order-entry-api-key>

That key is provisioned through builder or BSL institutional onboarding and controls the order-entry lane and rate tier. The signed action inside the batch still needs valid account authorization and risk checks.

Nonce handling

Each account uses a windowed nonce with a window of 256. The admission precheck excludes the action's own already-claimed pending nonce, so an HMAC- or agent-self-submitted order is never rejected for its own in-flight claim. Duplicate protection is preserved: a second action that reuses an existing (account, nonce) pair is rejected. Submit monotonically increasing nonces per agent and you can have multiple in-flight actions inside the window.

Client order id on agent orders

clientOrderId (cloid) is part of the signed action payload for direct-HTTP and FIX orders: it is folded into the signing hash and the derived order id, and it is propagated into the order-entry result and the drop-copy stream.

On older delegated agent batch paths, cloid may not be carried into the engine action payload. For durable client-side deduplication, use the separate Idempotency-Key header; use clientOrderId for strategy reconciliation after the engine has accepted or rejected the order.

Security notes

  • Show apiSecret and apiPassphrase only once.
  • Store only hashes/encrypted secrets server-side; never log the raw response.
  • Use policy, IP allowlists, expiry, and narrow scopes for production bots.
  • Revoke compromised agents with POST /api/agents/revoke.
  • Rotate credentials before sharing access with a new bot or operator.