This walks the shortest honest path to one working result: a signature produced by a key the service manages for you. It assumes your operator has already issued you a tenant API key and secret.
What you need
Set these once for the session. The API key and secret come from your operator — they are not self-service.
export API_URL=https://tee-api.hypetrade.xyz/v1
export API_KEY=your-api-key
export API_SECRET=your-api-secretCreate a workspace
A workspace is a password-protected container. Pick a slug and a strong password; you will need the password again to open it.
curl -X POST "$API_URL/workspaces" \
-H "X-Api-Key: $API_KEY" \
-H "X-Api-Secret: $API_SECRET" \
-H "content-type: application/json" \
-d '{"slug":"demo","password":"a-strong-workspace-password"}'Slugs are lowercase letters, digits, and hyphens, must start alphanumeric, and
run to 63 characters. A 201 returns the workspace record.
Mint a workspace token
This is the unlock. The token that comes back holds the workspace open, and its lifetime is the session.
curl -X POST "$API_URL/auth/token" \
-H "X-Api-Key: $API_KEY" \
-H "X-Api-Secret: $API_SECRET" \
-H "content-type: application/json" \
-d '{"workspace":"demo","password":"a-strong-workspace-password"}'{
"token": "eyJhbGciOi...",
"expiresAt": "2026-08-18T12:34:56.000Z",
"workspace": "demo",
"scopes": ["read", "write", "sign"]
}Capture the token — every remaining step uses it:
export TOKEN=the-token-valueCreate an account
An HD account generates its own mnemonic. The mnemonic is never returned in
a response body; generatedSecret: true only tells you one was created.
curl -X POST "$API_URL/accounts" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"displayName":"Treasury","kind":"HD"}'The account slug is generated by the service, not chosen by you. It comes back
at account.slug:
{
"account": {
"slug": "treasury-a1b2",
"displayName": "Treasury",
"kind": "HD",
"locked": false,
"defaultNetwork": "ethereum",
"wallets": []
}
}export ACCOUNT=treasury-a1b2Derive wallets
A new account starts empty. Derive wallets to get addresses.
curl -X POST "$API_URL/accounts/$ACCOUNT/wallets" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"count":2}'{ "before": 0, "after": 2 }Read the addresses
curl "$API_URL/accounts/$ACCOUNT/wallets" \
-H "Authorization: Bearer $TOKEN"Each wallet carries its addresses, one per VM. Each is fixed to the network the account was created with — see Accounts, wallets, and addresses:
{
"wallets": [
{
"id": 1,
"tags": [],
"addresses": [
{ "publicKey": "0xabc…", "vm": "evm", "network": "ethereum", "chainId": 1 }
]
}
]
}Sign a message
Signing addresses a public key directly — you never walk account → wallet → address to sign. Resolution is scoped to your own workspace.
curl -X POST "$API_URL/sign/message" \
-H "Authorization: Bearer $TOKEN" \
-H "content-type: application/json" \
-d '{"address":"0xabc…","message":"hello from tee-docker"}'{ "address": "0xabc…", "signature": "0x…" }Lock up when you are done
Revoking ends this token’s lease. It is the only token you minted here, so the workspace locks with it.
curl -X DELETE "$API_URL/auth/token" \
-H "Authorization: Bearer $TOKEN"What just happened
You used both credential tiers. Steps 1 and 2 used tenant credentials —
the only two calls that do. Everything after used the workspace token, which
carried the default read, write, and sign scopes.
Next
- Workspaces and tokens — session lifetime, refresh, and what expiry looks like
- Scopes and permissions — including why
exportis different - Send transactions — build, simulate, submit
- Errors — the full code catalog and what to do about each