overview
api.codehunt.com is a cloud service that provides remote access to the code exploration and test generation functionality of Pex for .NET programs.
In particular, api.codehunt.com powers the Code Hunt game.
api.codehunt.com is a cloud service that provides remote access to the code exploration and test generation functionality of Pex for .NET programs.
In particular, api.codehunt.com powers the Code Hunt game.
The APIs are subject to change. The state managed by api.codehunt.com may reset at any time.
The following number not only include registered users playing on www.codehunt.com, but also anonymous users and accounts that access api.codehunt.com directly via the documented REST APIs.
Users: 5981893
User Programs: 15407910
User Explorations: 12738135
Programs: 7444221
Explorations: 6264758
Each API is given via its method (GET
or POST
), its path (/api/something
), an optional request body, and a list of possible response codes and bodies.
Requests and response bodies, if any, are in JSON format, and are specified using TypeScript interface syntax.
Most APIs require a special authorization header with a bearer token.
If you do not send the following header with the APIs, you will get a 401 Unauthorized
status code.
Authorization: Bearer $ACCESS_TOKEN
There are two ways to get an access token: 1) anonymously (which creates a new anonymous user account on-the-fly), or 2) by referring to a regular user account.
To get an anonymous $ACCESS_TOKEN
, do the following.
POST /api/token?grant_type=client_credentials&client_id=anonymous&client_secret=anonymous response 200 OK body: TokenInfo interface TokenInfo { access_token: string; }
Anonymously obtained access tokens have severe usage restrictions.
If you want to obtain a regular user account (represented by the pair client_id
and client_secret
), send a request by email to codehunt@microsoft.com.
You can merge all data from an anonymously obtained account into a regular user account. You need to obtain an access token for the anonymous account from which data will be taken; then call the following API authorized by the regular target user to whom the data is copied.
POST /api/merge request body: MergeInfo response 204 No Content response 400 Bad Request response 401 Unauthorized (no valid Authorization header in request) response 412 Precondition Failed (authorized user is anonymous, or from_access_token is not an anonymous user, or one that has been merged already) interface MergeInfo { from_access_token: string; }
GET /api/languages response 200 OK body: Languages interface Languages { languages: string[]; }
You can post a program, and obtain a $programId
.
POST /api/programs request body: Program response 201 Created body: Id response 401 Unauthorized (no valid Authorization header in request) interface Program { language: string; text: string; } interface Id { id: string; }
If you created a program, then you can retrieve it again. Additionally, if you have created a universe, you can retrieve the programs explored by users for any level (or challenge) in the universe.
GET /api/programs/$programId OR Get /api/programs/$programId?universeid=$UNIVERSE_ID&challengeid=$CHALLENGE_ID&userid=$USER_ID response 200 OK body: Program response 400 Bad Request ($programId is not well-formed) response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to read $programId) response 404 Not Found ($programId does not exist)
Convert a program to another language.
POST /api/translate?language=$TO_LANGUAGE request body: Program response 200 OK body: TranslationInfo interface TranslateInfo { kind: string; } interface TranslateInfoSuccess extends TranslateInfo { program: Program; }
The ?language=$TO_LANGUAGE
parameter is mandatory. The language must be one of the languages returned by the /api/languages
API.
In the response, kind
is either "Translated"
or CompilationError
. If it is Translated
, then the response is of type TranslateInfoSuccess
and holds the translated program
. If it is CompilationError
, then the response is actually of type ExplorationStatusCompilationError
(with some irrelevant fields being null).
You can start the exploration of a single program, or of a program against another (challenge) program. The exploration is identified by an $explorationId
.
POST /api/explorations request body: Exploration response 201 Created body: Exploration response 400 Bad Request (some input is not well-formed) response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to read programId) response 429 Too Many Requests (limit exceeded) interface Exploration { programId?: string; program?: Program; challengeId?: string; challenge?: Program; }
In case of a 201 Created
and 429 Too Many Requests
, the following header fields may be set in the response, giving insights into the current rate limiting.
X-RateLimit-Limit (maximum number explorations in current time window) X-RateLimit-Remaining (remaining explorations in current time window) X-RateLimit-Reset (epoch seconds indicating when the next time window starts)
If the authorized user has an overall total limit of explorations, then the following fields may also be present in the response:
X-Limit-Limit (maximum number of explorations) X-Limit-Remaining (remaining explorations)
Either the programId
or the program: { "language": "CSharp", "text", "... program text ..." }
field must be present in the request body argument.
Both challengeId
and challenge
can be missing if you just want to explore a single program in isolation.
Otherwise either challengeId
or challenge
must be present.
If you created an exploration, then you can retrieve it again.
GET /api/explorations/$explorationId response 200 OK (exploration finished) body: ExplorationStatus response 206 Partial Content (exploration still going on) body: ExplorationStatus response 400 Bad Request ($explorationId is not well-formed) response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to read explorationId) response 404 Not Found ($explorationId does not exist) response 503 Service Unavailable (system overloaded)
In case of a 503 Service Unavailable
, the following header fields may be set.
Retry-After seconds client must wait before trying again
In case of a 200 OK
or 206 Partial Content
, the body contains the details of the exploration status.
interface ExplorationStatus { id: string; // exploration id programId: string; // program id corresponding to program supplied when exploration was created challengeId?: string; // exploration id corresponding to exploration supplied when exploration // was created, if any kind: string; // see below isComplete: bool; // whether the exploration is still no longer ongoing; // isComplete ==> status code 200; (not isComplete) ==> status code 206 }
The kind
property holds one of the following string values.
Initializing // ==> isComplete == false; can heppen when exploration is still initializing InternalError // ==> isComplete == true; should not happen Canceled // ==> isComplete == true; should not happen Compiling // ==> isComplete == false; AnalyzingDependencies // ==> isComplete == false; CompilationError // ==> isComplete == true; can happen if program doesn't compile BadPuzzle // ==> isComplete == true; can happen if Puzzle method is malformed BadCodingDuel // ==> isComplete == true; can happen if the signatures of the Puzzle methods // of the program and the exploration don't match BadDependency // ==> isComplete == true; can happen if the Puzzle method tries // to break out of the sandbox TestCases // usually starts out with isComplete == false, then later finally // switches to isComplete == true
Depending on the kind
property, the status might actually be an instance of one of the following more refined subtypes
which reveal more details.
// if (kind == "InternalError") interface ExplorationStatusInternalError extends ExplorationStatus { exception: string; // only for internal debugging purposes, not something to show to the end-user to } // if (kind == "CompilationError") interface ExplorationStatusCompilationError extends ExplorationStatus { documentationUrl: string; // generic help page for compiler errors of the given language errors: CompilerError[]; // to be shown to end-user } interface CompilerError { errorNumber: string; // e.g. "CS0123" errorDocumentationUrl: string; // specific help page for that errorNumber errorText: string; line: number; column: number; } // if (kind == "BadPuzzle") interface ExplorationStatusBadPuzzle extends ExplorationStatus { description: string; // to be shown to end-user } // if (kind == "BadDependency") interface ExplorationStatusBadDependency extends ExplorationStatus { referencedTypes: string[]; // to be shown to end-user } // if (kind == "BadCodingDuel") interface ExplorationStatusBadCodingDuel extends ExplorationStatus { errors: string[]; // to be shown to end-user } // if (kind == "TestCases") interface ExplorationStatusTestCases extends ExplorationStatus { hasWon: bool; // only relevant if challengeId is present names?: string[]; // names of parameters, if any testCases: TestCase[]; } interface TestCase { status: string; // one of the following: "Failure", "Inconclusive", "Success" values?: string[]; // pretty-printed strings representing argument values, // corresponding to parameter names, if any summary: string; anyExceptionOrPathBoundsExceeded: bool; message: string; code?: string; // test case code, if at explored function has at least one parameter exception?: string; // exception message, if any stackTrace?: string; // stack trace, if any }
You can query the most recent programs you explored to win a particular challenge. Additionally, if you have created a universe you can view the program explorations of each of the users who have explored the universe.
GET /api/history/$challengeId?count=$COUNT&language=$FILTER_LANGUAGE OR Get /api/history/$challengeId?universeid=$UNIVERSE_ID&userid=$USER_ID&count=$COUNT&language=$FILTER_LANGUAGE response 200 OK (exploration finished) body: History response 401 Unauthorized (no valid Authorization header in request) interface History { items: HistoryItem[]; // most recently submitted programs } interface HistoryItem { programId: string; time: number; // epoch seconds }
The ?count=$COUNT
parameter is optional; if it is missing, the default is 100; if it is present, it must be a number between 1 and 1000. This parameter represents the maximum number of recent programs to retrieve.
The ?language=$FILTER_LANGUAGE
parameter is optional; if it is missing, all history entries are returned; if it is present, the result is filtered, and only programs of the given language are returned. The language must be one of the languages returned by the /api/languages
API.
You can query the current rate limits.
GET /api/limit response 200 OK body: Limit response 401 Unauthorized (no valid Authorization header in request) interface Limit { explorations: ExplorationsLimit } interface ExplorationsLimit { rateLimit: RateLimit; limit?: Limit; } interface RateLimit { limit: number; // maximum number explorations in current time window remaining: number; // remaining explorations in current time window reset: number; // epoch seconds indicating when the next time window starts } interface Limit { limit: number; // maximum number of explorations remaining: number; // remaining explorations }
You can change user settings.
POST /api/settings request body: User // score, rating, won is ignored response 200 OK body: User response 401 Unauthorized (no valid Authorization header in request)
You can query the settings of a user.
GET /api/settings response 200 OK body: User response 400 Bad Request (some input is not well-formed) response 401 Unauthorized (no valid Authorization header in request)
You can request to delete a user account.
DELETE /api/settings response 202 Accepted (user account is scheduled for deletion; account can still be used while issued access token are valid, but no new tokens can be obtained) response 401 Unauthorized (no valid Authorization header in request)
Users have the following shape.
interface User { userIdHash: string; nickname: string; twitterHandle: string; url: string; experience: string; preferredLanguage: string; score: number; // universe-dependent rating: number; // universe-dependent won: number; // universe-dependent wonLimit: number; // universe-dependent lastScored?: number; // universe-dependent bestWorldIndex?: number; // universe-dependent bestLevelInWorldIndex?: number; // universe-dependent startTime?: number; // when universe begins, if at all, in epoch seconds endTime?: number; // when universe ends, if at all, in epoch seconds now: number; // in epoch seconds noSounds?: boolean; nameId? string; }
Retrieves the top users who scored highest during the last rolling 30 days.
GET /api/leaderboard?count=$COUNT&continuation=$CONTINUATION response 200 OK body: Leaderboard interface Leaderboard { continuation?: string; users: User[]; startTime?: number; // when universe begins, if at all, in epoch seconds endTime?: number; // when universe ends, if at all, in epoch seconds now: number; // in epoch seconds statistics: UniverseStatistics; } interface UniverseStatistics { users: number; // how many users ever visited at this universe usersWhoScored: number; // how many users scored at least once scored: number; // how many times users scored (not the sum of all users' score values) }
The ?count=$COUNT
parameter is optional; if it is missing, the default is 20; if it is present, it must be a number between 1 and 1000. This parameter represents the maximum number of top users to retrieve.
The ?continuation=$CONTINUATION
parameter is optional; if it is missing, the default is to enumerate from the beginning; otherwise, it can be set to the continuation value retrieved by an earlier operation. Note that users may repeat in later queries; those should be ignored.
You can query all worlds.
GET /api/worlds response 200 OK body: Worlds response 401 Unauthorized (no valid Authorization header in request) GET /api/worlds/$worldId response 200 OK body: World response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden This world is not yet unlocked for the authorized user. response 404 Not found GET /api/levels/$worldId?language=$LANGUAGE response 200 OK body: Levels response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden This world is not yet unlocked for the authorized user. response 404 Not found GET /api/levels/$worldId/$levelId?language=$LANGUAGE response 200 OK body: Level response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden This world is not yet unlocked for the authorized user. response 404 Not found
The ?language=$LANGUAGE
parameter is optional; if it is missing, the default language CSharp is used. Otherwise, the language must be one of the languages returned by the /api/languages
API.
Worlds and levels have the following shape.
interface Worlds { name: string; logoSplashUrl?: string; logoMenuUrl?: string; startTime?: number; // when universe begins, if at all, in epoch seconds endTime?: number; // when universe ends, if at all, in epoch seconds now: number; // in epoch seconds worlds: World[]; } interface World { id: string; // id of world index: number; name: string; clientUnlocked: bool; // specific to authorized user levels: number; levelsCompleted: number; // specific to authorized user score: number; // specific to authorized user rating: number; // specific to authorized user } interface Levels { levels: Level[]; } interface Level { id: string; // id of level indexInWorld: number; description: string; programId: string; // initial program; use /api/history query for user programs challengeId?: string; completed: bool; multiplier: number; score: number; // specific to authorized user rating: number; // specific to authorized user ratingDescription?: string; // specific to authorized user attempts: number; // specific to authorized user hints: number; // specific to authorized user started?: number; // specific to authorized user; in epoch seconds won?: number; // specific to authorized user; in epoch seconds }
You can post a universe, and obtain a $universeId
.
POST /api/universes request body: XML-encoded universe, up to 32KB after deflate-compression of UTF8 encoded XML response 201 Created body: Id response 400 Bad Request (empty or too large XML body) response 401 Unauthorized (no valid Authorization header in request) interface Id { id: string; }
If you created a universe, then you can retrieve it again.
GET /api/universes/$universeId response 200 OK body: XML response 400 Bad Request ($universeId is not well-formed) response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to read $universeId) response 404 Not Found ($universeId does not exist)
GET /api/universes?count=$COUNT&continuation=$CONTINUATION response 200 OK body: Universes response 400 Bad Request ($COUNT is not valid) response 401 Unauthorized (no valid Authorization header in request)
PUT /api/Universes/$UniverseId?hide=$HIDE response 200 OK body: XML response 400 Bad Request ($universeId is not well-formed) response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to modify $UniverseId) response 404 Not Found ($universeId does not exist)
interface Universes { continuation?: string; now: number; // in epoch seconds universes: Universe[]; } interface Universe { id: string; name: string; logoMenuUrl?: string; logoSplashUrl?: string; startTime?: number; // when universe opens (if limited time), in epoch seconds endTime?: number; // when universe closes (if limited time), in epoch seconds created: number; // when universe was created, in epoch seconds hidden: boolean; }
All requests/responses marked as universe-dependent can be executed relative to a particular universe. Send the following header with a request to access a non-default universe.
Universe: $universeId
You can query which universes a user has started exploring.
GET /api/useruniverses?count=$COUNT&continuation=$CONTINUATION response 200 OK body: UserUniverses response 401 Unauthorized (no valid Authorization header in request) response 403 Forbidden (authorized user is not allowed to read $universeId) interface UserUniverses { continuation? string; now: number; // in epoch seconds userUniverses: UserUniverse[]; } interface UserUniverse { id: string; // universe id name: string; logoMenuUrl: string; logoSplashUrl: string; startTime?: number; // when universe begins, if at all, in epoch seconds endTime?: number; // when universe ends, if at all, in epoch seconds started: number; // when user entered this universe, in epoch seconds lastAccess: number; // when universe was last accessed, in epoch seconds lastScored?: number; // when user score in this universe last increased, in epoch seconds won: number; wonLimit: number; rating: number; score: number; }
The ?count=$COUNT
parameter is optional; if it is missing, the default is 10; if it is present, it must be a number between 1 and 100. This parameter represents the maximum number of top users to retrieve.
The ?continuation=$CONTINUATION
parameter is optional; if it is missing, the default is to enumerate from the beginning; otherwise, it can be set to the continuation value retrieved by an earlier operation. Note that users may repeat in later queries; those should be ignored.
Time is measured in epoch seconds, which are seconds since 1970 (UTC).