Skip to content
Back to App

Authentication

The Simulator API uses bearer token authentication. You generate an API key in the TestMax web app, exchange it for a token via the Auth/loginKey endpoint, and then include that token in every subsequent request.

Authentication flow

  1. Generate an API key

    Log into test-max.com and navigate to Settings > API Access (a Pro feature). Generate your key and copy it immediately — it will not be shown again. Each account has a single API key; generating a new one replaces the old key.

  2. Exchange the API key for a token

    Call POST Auth/loginKey with your email address and API key. The response contains a bearer token.

  3. Include the token in all requests

    Pass the token in the Authorization header:

    Authorization: Bearer <your-token>
  4. Refresh or validate as needed

    Tokens expire after 24 hours. Use POST Auth/validate to exchange a valid token for a fresh one, and POST Auth/logout to invalidate a token when done.


POST Auth/loginKey

Authenticate with your email address and API key to receive a bearer token.

URL: POST /api/sim/Auth/loginKey

Request body

{
"userName": "your@email.com",
"apiKey": "your-api-key-here"
}
FieldTypeRequiredDescription
userNamestringYesYour TestMax account email address
apiKeystringYesThe API key generated in Settings

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null,
"token": "sim_4f8a2b1c9d3e..."
}
FieldTypeDescription
tokenstringBearer token to use in subsequent requests. Valid for 24 hours.

Example

import urllib.request
import json
API_URL = "https://api.test-max.com/api/sim"
# Step 1: Authenticate
login_data = json.dumps({
"userName": "your@email.com",
"apiKey": "your-api-key-here"
}).encode()
req = urllib.request.Request(
f"{API_URL}/Auth/loginKey",
data=login_data,
headers={"Content-Type": "application/json"}
)
resp = json.loads(urllib.request.urlopen(req).read())
if resp["success"]:
token = resp["token"]
print(f"Authenticated. Token: {token[:20]}...")
else:
print(f"Login failed: {resp['errorMessage']}")

Error responses

ScenarioerrorCodeerrorMessage
Invalid, missing, or revoked credentials3"Invalid credentials"
Account is not on a Pro plan9"Pro subscription required for the Simulator API"
Too many login attemptsHTTP 429"Too many requests" (with a Retry-After header)

All credential failures — wrong key, unknown email, unverified email, or a disabled account — return the same "Invalid credentials" response, so the endpoint cannot be used to probe which accounts exist.


POST Auth/validate

Validate an existing token. On success the old token is rotated: the response carries a fresh token in the newToken field, and you should use it for all subsequent requests.

URL: POST /api/sim/Auth/validate

Request headers

Authorization: Bearer <your-token>
Content-Type: application/json

Request body

{}

An empty JSON body is required.

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null,
"newToken": "sim_7c2d9e0f1a4b..."
}
FieldTypeDescription
newTokenstringA fresh token replacing the one you sent. Valid for 24 hours.

If the token you sent is invalid or expired, the response is {"success": false, "errorCode": 1, "errorMessage": "Invalid session", "newToken": null} — re-authenticate with Auth/loginKey.

Auth/validate is itself rate limited to 60 calls per minute per account — far above the once-a-minute keepalive a bot needs. Past that the response is HTTP 429 with a Retry-After header and the body {"success": false, "errorCode": 429, "errorMessage": "Too many requests"}; the token you sent is untouched, so retry with it after the wait. Note that this is an HTTP error status, not a 200 carrying success: false — a client that raises on non-2xx (Python’s urllib, for one) will throw here rather than reach the success check.

Example

req = urllib.request.Request(
f"{API_URL}/Auth/validate",
data=b"{}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
)
resp = json.loads(urllib.request.urlopen(req).read())
if resp["success"]:
token = resp["newToken"] # The old token is rotated — use the new one
print("Token renewed")
else:
print("Token expired — re-authenticate")

POST Auth/logout

Invalidate a token. After logout, the token can no longer be used for any API calls.

URL: POST /api/sim/Auth/logout

Request headers

Authorization: Bearer <your-token>
Content-Type: application/json

Request body

{}

Response

{
"success": true,
"errorCode": 0,
"errorMessage": null
}

Example

req = urllib.request.Request(
f"{API_URL}/Auth/logout",
data=b"{}",
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
)
resp = json.loads(urllib.request.urlopen(req).read())
print("Logged out" if resp["success"] else "Logout failed")

GET Status/ping

Health check endpoint. Returns a simple response indicating the API is operational. No authentication required.

URL: GET /api/sim/Status/ping

Response

A plain-text body (not JSON):

pong

Example

Terminal window
curl https://api.test-max.com/api/sim/Status/ping

Using the token in your scripts

Once authenticated, include the token in every request:

import urllib.request
import urllib.error
import json
import time
API_URL = "https://api.test-max.com/api/sim"
TOKEN = "your-token-here"
def api(path, body=None, retries=10):
"""Call any Simulator API endpoint, retrying 429/503 after Retry-After."""
data = json.dumps(body).encode() if body else None
req = urllib.request.Request(
f"{API_URL}/{path}",
data=data,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {TOKEN}"
}
)
for _ in range(retries):
try:
return json.loads(urllib.request.urlopen(req).read())
except urllib.error.HTTPError as e:
if e.code not in (429, 503):
raise
time.sleep(min(int(e.headers.get("Retry-After", "1") or "1"), 5))
raise RuntimeError(f"{path} kept answering 429/503")
# Now use it for any endpoint
accounts = api("Account/search", {"onlyActiveAccounts": True})
contracts = api("Contract/search", {"searchText": "NQ"})

Security best practices

  • Never hardcode API keys in your scripts. Use environment variables: os.environ["TESTMAX_API_KEY"]
  • Do not commit tokens or keys to version control. Add them to .gitignore or use a .env file
  • Rotate your key periodically in Settings > API Access — generating a new key replaces and revokes the old one immediately
  • Log out when done by calling Auth/logout to invalidate tokens after a script completes