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 type | Use |
|---|---|
api_agent | Standard server-side API bots, private reads, and delegated HTTP workflows |
institutional_agent | BSL/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 type | Maximum lifetime | Credential support |
|---|---|---|
browser_session | 30 days | No HMAC/Ed25519 credential; wallet-created runtime signer |
mobile_session | 24 hours | No HMAC/Ed25519 credential; mobile handoff signer |
api_agent | 180 days | HMAC or Ed25519 HTTP machine auth |
institutional_agent | 90 days | HMAC 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 type | Returned secrets | Use |
|---|---|---|
| HMAC | apiKeyId, apiSecret, apiPassphrase | FIX Logon, SC-Auth-Version: 2, durable bot/API access |
| Ed25519 | apiKeyId, public key only | HTTP 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
}
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 tag | Value |
|---|---|
553 | apiKeyId |
554 | apiSecret:apiPassphrase |
49 | Bundle fix.orderEntry.senderCompIdTag49 for order entry, or fix.dropCopy.senderCompIdTag49 for drop-copy |
56 | Bundle fix.targetCompIdTag56, normally SENTICORE |
57 | order_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
apiSecretandapiPassphraseonly 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.