Skip to content
API documentation
View as MarkdownOpen in Claude

Start here

Environments

The API has two live environments: production, and a sandbox for building and testing your integration. This page covers both, explains how to reach the API (HTTPS, from your servers only), and shows how to monitor it.

Base URLs

EnvironmentBase URLStatusUse it for
Productionhttps://api.main-team.org/v1AvailableReal students, real exams, real results
Sandboxhttps://apisnd.main-team.org/v1AvailableBuilding and testing your integration without touching real data

Every API route is under /v1. The version is part of the path, so /v1 stays stable until a future major version, and a new major version would get a new prefix. The bare host, https://api.main-team.org/, answers 404 not_found, as does any path under /v1 that is not a route. See Versioning for what can change within /v1.

Keep the base URL in configuration, not in code, so that moving between environments only means changing a setting:

# production
MTO_API_BASE=https://api.main-team.org/v1
# sandbox
# MTO_API_BASE=https://apisnd.main-team.org/v1

The official client libraries accept only the production base URL. To call the sandbox, send plain HTTPS requests, or use a client of your own such as the one in Build your own client.

Production

Production is the live platform. Every request acts on real data:

  • A student you register is a real account on the platform.
  • A sign-in link you mint signs a real browser in as that student.
  • An application you create enters a real student for a real exam session.
  • Certificates and reports are the students' real results.

There is no test mode and no "dry run" flag in production. The API has no route that deletes a student, so a student registered by mistake stays registered. Build and test your integration against the sandbox first. When you move to production, agree your first writes with us (info@main-team.org). For example, use one agreed test student rather than inventing new ones on every run.

Warning

Do not point automated tests or CI pipelines at production. Read-only checks, such as validate-me or listing organizations, are safe. Anything that registers students or creates applications is not: run it against the sandbox.

Sandbox

The sandbox is a separate copy of the platform for integration work. It runs the same release of the API as production and follows each production release. It has its own data and holds no real students, and nothing you do there reaches production.

ProductionSandbox
Base URLhttps://api.main-team.org/v1https://apisnd.main-team.org/v1
AccountsIssued by an operator on requestSeparate accounts, issued by an operator from the sandbox's own panel. Ask at info@main-team.org
CredentialsWork only in productionWork only in the sandbox
Reference dataReal countries, grades, organizations and examsSeeded reference data. The organization _ids are the same as in production; there may be fewer exams
StudentsReal studentsOnly students registered for testing. Registration (POST /v1/student) is open, with the same permissions as in production
EmailsReal emails can be sent by the platformNo emails are sent
PaymentsReal cards, real moneyStripe test mode: test cards only, no real money moves. See Test payments
Rate limitsSee Rate limitsThe same as production
"Try it" console on the reference pagesNot availableAvailable, with a token you paste
Official client librariesSupportedNot supported: call the sandbox over HTTPS directly

A production key does not work in the sandbox, and a sandbox key does not work in production. Mixing them up gives the usual 401 unauthorized (see Authentication), so check that your key and your base URL come from the same environment.

The routes, the request and response formats, the token rules, the permission model and the organization ids are the same in both environments, so code written against the sandbox needs only a new base URL and new credentials to run in production.

What to know when you test there:

  • Use invented details. Every student in the sandbox is a test student. Don't enter real people's names or email addresses.
  • No emails arrive. Nothing reaches a mailbox, including the 6-digit code the panel uses to confirm a student's email address. So the sandbox's panels don't ask students to confirm their address, and emailConfirmed stays false for the students you register there. See Email confirmation.
  • Sign-in links open the sandbox's panels. A link you mint in the sandbox opens on authsnd.main-team.org and signs the student in to the organization's sandbox panel, never to production's. A sandbox panel is at snd.<brand>.org where production's is my.<brand>.org, for example snd.stemolympiad.org. Open it in a separate browser profile from any production session.
  • The same rate limits. 100 requests per 60 seconds per account and per operation, as in production, and the same limit per client address in front of the API. Don't load-test the sandbox; ask us if you need to measure throughput.
  • The data may be reset. A reset removes the students and applications created since the last one. We tell you in advance.
  • No availability guarantee. The sandbox can be briefly unavailable, for example while it is updated.

Test payments

The API never charges anyone. When an exam has a price, the student pays for the application in the organization's panel, by card. The sandbox's panels take these payments through Stripe in test mode, so no real money moves and no real card is charged.

To test a payment, create an application for an exam with a price, send the student to the panel with a sign-in link, and pay with one of Stripe's test cards:

Card numberWhat happens
4242 4242 4242 4242The payment succeeds without authentication
4000 0025 0000 3155The payment asks for 3D Secure authentication first
4000 0000 0000 9995The payment is declined for insufficient funds

With each card, enter any future expiry date, any 3-digit CVC and any postal code. The panel takes payment by card only, on Stripe's checkout page, so a test card is all you need. Stripe lists more test cards, for other declines and authentication cases, at https://docs.stripe.com/testing.

Warning

Test cards are for the sandbox. In production only real cards work and every payment is real, so never use a test card there.

The "Try it" console

The reference pages have an interactive "Try it" console. It sends requests only to the sandbox. It never calls production: the production API accepts no requests from browser pages (see below), and you should never paste production credentials into a web page anyway.

To use it, sign a token with your sandbox apiKey and apiSecret on your own machine (see Authentication) and paste the token. Never paste your apiSecret: the console refuses anything that looks like one and sends nothing. The token is kept only in the page's memory, never stored.

Call the API from your servers

The API is for server-to-server use.

  • No browser calls. Production sends no CORS headers, so a web page served from another origin cannot call the API. This is on purpose. A browser integration would need your apiSecret in the page, which hands it to every visitor. The sandbox accepts browser requests from one origin only, these documentation pages, for the "Try it" console.
  • No mobile or desktop apps. For the same reason, never put credentials in an app you distribute. Anything shipped to users can be extracted.
  • Put your own backend in between. Your web or mobile front end talks to your server; your server holds the secret, signs tokens and calls the API.

When a student needs to reach their panel, your server mints a sign-in link and redirects the browser to it. The browser never sees your token. See Sign-in links.

HTTPS only

Always use https://, and never send a request to http://. A request sent over plain HTTP has already carried your Authorization header across the network unencrypted, whatever the server does next. Put https:// in your base URL and do not let your HTTP client follow redirects to other hosts.

Responses carry Strict-Transport-Security, so browsers and clients that honor it stay on HTTPS for this host.

Response headers to know

HeaderWhenMeaning
X-Request-IdEvery responseThe id of this request. Send your own (letters, digits and ._:;=+/@-, 1 to 256 characters) to have it reused; a value with any other character, or a longer one, is replaced. Without one, an id is generated for you. Treat it as an opaque string, because the format of a generated id can vary. It also appears as error.request_id in error bodies. Log it.
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-ResetEvery request counted against your rate limit, except a 429Your budget on this operation: the limit, what is left, and the seconds until the window resets. See Rate limits
Retry-After429 onlyThe number of seconds to wait before you send again. A request sent sooner is refused too
Content-TypeEvery response with a bodyapplication/json; charset=utf-8 for JSON. Certificate and report downloads carry the file's own type (normally application/pdf), or application/octet-stream when none is recorded
Content-DispositionDownloadsThe file name. Prefer its filename*=UTF-8''… form

Details on request ids, bodies and envelopes are in Requests and responses.

Monitoring: GET /v1/health

GET /v1/health tells you whether the API is up. It needs no token and does not count against any account's rate limit. The network in front of the API still limits how fast each client address may send requests, with or without a token, so poll at a steady interval, such as once a minute, not in a tight loop.

curl -s https://api.main-team.org/v1/health
{
  "success": true,
  "message": "Request completed successfully.",
  "data": { "status": "ok" }
}

Treat any other status code, or no response within a few seconds, as "down".

/v1/health only checks that the service is running. It does not check your credentials, your roles or the data behind the routes. To check your integration end to end, also make one authenticated call on a schedule, for example GET /v1/api-account/validate-me every few minutes:

CheckWhat a failure means
GET /v1/health is not 200The API is down or unreachable from your network
validate-me is 401Your token, secret, clock or account is the problem (see the 401 checklist)
validate-me is 403Your account lost the api/* role (see Permissions)
validate-me is 5xx while /v1/health is 200The API is running but has a problem on our side. Retry, and report it with the request_id if it persists

validate-me counts against your rate limit like any other operation (100 requests per 60 seconds on that operation), which leaves plenty of room for a check every minute.

Timeouts

Set explicit timeouts on your HTTP client instead of relying on defaults. Reasonable starting values are 5 seconds to connect and 30 seconds to read a JSON response. Allow longer for certificate and report downloads, which stream PDF files. The API closes a connection whose request has not arrived completely within 30 seconds. See Retries and idempotency for which requests are safe to retry after a timeout.

Time and dates

Base your token timestamps (iat, exp) on UTC seconds since the Unix epoch, and keep your server clock synchronized with NTP. The API tolerates at most 30 seconds of clock difference. Date formats in request and response bodies are described in Requests and responses.

Versions and deprecation

The version of the API these pages describe is shown in the API reference. What changes, and when, is recorded in the changelog, with a page for every version.

  • Before anything is removed or changed incompatibly, you get at least 6 months' notice, through the changelog and through Deprecation and Sunset headers on the affected responses.
  • Removals happen only in a new major version, which gets a new path prefix.
  • Your code should ignore response fields it does not recognize, because new fields can be added within /v1.

The full policy is in Versioning.

Support

Write to info@main-team.org. Include the environment, the request_id, the time in UTC and the operation you called. Never include your apiSecret or a token. See Support.

Search the API documentation

Guides, endpoints by name, path or permission, and error codes such as not_found.