Twinmark / Developer reference
A living city.
An open interface.
Read the companies. Follow the markets. Connect to the world through the same player interfaces that power Twinmark.
01 / Start here
One account.
One world.
This reference describes the web app’s existing HTTP interface. Player requests use the same origin as the game and the signed-in player’s cookie. Money, orders, mission progress and permissions are checked on the server.
Player endpoints are available on the game deployment after sign-in. The public twinmark.xyz site currently serves documentation and read-only token and launch status; its player endpoints remain closed.
Read public token status
No account is needed. Network addresses and live figures can be null when disconnected or stale. Check chain.healthy before presenting a reading as current.
curl https://twinmark.xyz/api/tokenGET/api/launchRead launch policy+
Returns sanitized policy and readiness information. source: "planned" means the current backend has not supplied a valid reading; rewards is then null. A published allocation or an active policy does not establish mainnet readiness. Check readyForMainnet separately.
Monetary values travel as decimal strings. Preserve their precision; use decimal or fixed-point arithmetic.
Use your own account. A client cannot grant itself funds, complete unverified work or advance the world clock.
02 / Your session
Authenticate.
Keep it private.
Sign-in sets the aic_session HttpOnly cookie. The web response contains player information, not the backend bearer token. Browser integrations run on the game origin and send cookies with their requests. Do not copy a session into a URL, a public client or source control.
POST/api/authSign in+
Returns ok and player, and sets the session cookie on success. Registration uses kind: "signup" with the same fields and an optional display. Send kind: "logout" to clear the session.
{ "kind": "login", "handle": "your-handle", "password": "your-password" }POST/api/authSign in with a wallet+
Request a one-time message, sign that exact message with the wallet, then submit { kind: "wallet", address, signature }. An optional display sets a new player’s display name. A signature signs a message; it is not a token deposit.
{ "kind": "wallet_nonce", "address": "0x…" }GET/api/sessionCheck your session+
Returns ok, signedIn, and, when signed in, player, seat, day and saved state. An expired session reads as signed out.
PUT/api/sessionSave player preferences+
The JSON request body is the player’s saved UI state. It is not the economy ledger: writing a balance, mission completion or reward into preferences does not award anything.
03 / Read the city
Follow the story
behind a price.
Most player reads use POST /api/act with a named kind. These requests need a signed-in session even when they do not change the world.
// Run on the signed-in game's own origin.
const response = await fetch("/api/act", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ kind: "companies" }),
});
const data = await response.json();
if (!response.ok || data.ok !== true) {
throw new Error(data.detail ?? "Request failed");
}
console.log(data.companies);| Kind | Additional fields | Response field |
|---|---|---|
companies | None | companies |
company | ticker | company |
prices | ticker, optional days | prices |
listings | None | listings |
quotes | instruments (IDs), optional size | quotes |
book | instrument, optional levels, size | book |
books | ticker | accounts |
profile | company (ID), seat | profile |
orders / fills | None | orders / fills |
GET/api/stateRead your game snapshot+
Returns the clock, season, portfolio, standings, inbox, calendar, quotes, operation, orders and missions. An optional numeric since query parameter advances the inbox cursor. Read ok and signedIn before consuming the snapshot.
04 / Player actions
Your choices.
Server-checked.
Actions can commit trades, spend game funds or change your career. Send them only after the player decides. Read instrument IDs from listings and mission IDs from the current snapshot.
POST/api/actPlace an order+
side is buy or sell. limit is an optional decimal string; tif is ioc or gtc. The numbers here are illustrative, not a quote. Use one idempotency key per intended order and retain it for a retry of that same request. Do not reuse it for a different trade.
{
"kind": "order",
"instrument": 1,
"side": "buy",
"quantity": 1,
"limit": "10.0000",
"tif": "gtc",
"idem": "a-unique-key-for-this-intended-order"
}POST/api/actCancel an order+
Use the order ID from your open orders. The response includes cancelled. Check the result; an order may have filled before cancellation reached the server.
{ "kind": "cancel_order", "id": 123 }POST/api/actAccept or progress a mission+
accept starts an available mission. step asks the server to check the current objective; it does not provide evidence on its own. abandon leaves a mission and extend requests an extension when supported. Eligibility, costs, evidence and reward limits belong to the server.
{ "kind": "accept", "id": "mission-id-from-your-snapshot" }POST/api/actFind work+
Requests a role at the company ID. Work screens submit their own validated actions. Taking a job does not complete a shift or guarantee a reward.
{ "kind": "job", "company": 1 }05 / Wallet & settlement
Know where
the money is.
Game balances, linked wallet balances and pending transfers are separate states. Read current wallet and network status before asking the player to sign anything. The active deployment determines supported contracts and withdrawal eligibility.
POST/api/actRead your wallet+
Returns wallet, including the account’s balances, linked address, transaction ledger and chain information when configured. Send { "kind": "chain_status" } to read the chain configuration separately.
{ "kind": "wallet" }POST/api/actLink a wallet+
Sign the returned one-time message, then submit { kind: "link", address, signature }. This proves ownership of the address. It does not transfer tokens. Launch wallets are permanently bound to their original player account. The legacy unlink action is unavailable for launch accounts.
{ "kind": "link_nonce", "address": "0x…" }For launch accounts, use wallet.launch.settlement to select the chain, token and economy vault. Approve the exact TWN amount on the token, then call deposit(uint256) on that vault. The game counts TWN to four decimal places (0.0001 TWN). On chain, TWN has settlement.decimals decimals: 18 on Robinhood Chain (custody version 4), where both calls take wei, game units × settlement.unit_scale (10^14), and the vault refuses any amount that is not a whole number of game units. Earlier custody versions used 4 decimals and a scale of 1. A raw token transfer does not create a game credit. Wait for the server’s confirmed tokens_in ledger event; its reference is chainId:transactionHash:logIndex.
redeem accepts a decimal-string tokens amount and a unique idem request key. Retain the same key when retrying an uncertain request, and retry only if wallet.withdrawal_idempotency is true. Launch responses reserve funds and include frozen authorization calldata, redemption and deadline. Validate its chain, vault, linked player and amount, then ask the player’s wallet to send it. Follow wallet.launch.withdrawal.status: authorized, settled or cancelled. A submitted transaction is not settled game state.
POST/api/actClaim or cancel a queued withdrawal+
On custody version 4, redeem admits a request and debits the game balance at once. It answers with request, queue_status and release_at (Unix seconds). After that time, launch-claim (or a retry of the same idem) returns the voucher calldata to send as above; claim_blocked explains a due request that cannot be signed yet. Before its voucher is signed, launch-cancel returns the tokens to the game balance but not the day’s withdrawal allowance. Today’s limits and the reward lock are in wallet.launch.withdrawal_terms.
{ "kind": "launch-claim", "request": 12 }
{ "kind": "launch-cancel", "request": 12 }POST/api/actVerify launch participation+
Before requesting an earned withdrawal, ask for the verified participation proof. If required is false, no enrollment transaction is needed. Otherwise the response contains chain_id, to and data for enrollWithProof. Validate these against the launch vault and linked player, send through the player’s wallet and wait for confirmation before requesting the withdrawal. The player pays network fees.
{ "kind": "launch-enrollment" }Resume or cancel an authorization
Declining the wallet’s transaction prompt leaves an already reserved withdrawal pending. Resume that same authorization; do not create another request. To cancel, the linked wallet calls invalidateNonce() on the economy vault. The game restores reserved funds only after canonical confirmation. Expiration, a browser timeout or a rejected signature is not a refund.
The launch funding model supports fully paid spot positions. Borrowing, short positions, derivatives and player-to-player matching are disabled. The server preserves deposited principal, gameplay rewards and market proceeds separately. Read settlement readiness independently from the public mainnet launch status; a working local rehearsal is not a mainnet launch.
Test assets are not mainnet assets. The public token status exposes only an allowlist of network information; it never includes custody keys or player balances.
06 / Live connection
The world
keeps moving.
POST/api/live-ticketRequest a socket ticket+
Requires a session and no request body. A short-lived, single-use ticket authenticates the world connection without revealing the backend bearer. Use the configured live endpoint for the deployment; on the HTTPS game host it is wss://<game-host>/v1/live.
The game client handles JSON world messages and binary street frames. The binary protocol is internal and may change with game releases. Reconnect with backoff, request a fresh ticket and discard stale world state after a disconnect.
07 / Responses
Check the result.
Then carry on.
The web interface commonly returns HTTP 200 with ok: false for a rejected action. HTTP success alone does not mean an order, reward or withdrawal succeeded. Read detail, and signedIn or type when present.
{
"ok": false,
"signedIn": false,
"detail": "not signed in"
}- Malformed JSON can return
400; session-dependent endpoints may return401. - The public launch gate returns
403for player API access. - A temporarily unavailable live-ticket service can return
503. - Back off on failures. Do not blindly retry money-moving requests; inspect the ledger or order state first.
This is a reference for the implemented web API, not a versioned third-party SDK or a guaranteed service level. Keep integrations aligned with the deployment you are using.