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.

disclaimer

The APIs are subject to change. The state managed by api.codehunt.com may reset at any time.

Internal Statistics

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

APIs

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.

authorization

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.

merging

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;
}

languages

GET /api/languages
    response 200 OK                     body: Languages

interface Languages {
    languages: string[];
}

programs

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)

translate

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).

explorations

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
}

history

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.

limit

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
}

settings

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;
}

leaderboard [universe-dependent]

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.

worlds, levels [universe-dependent]

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
}

universes

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

user universes

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

Time is measured in epoch seconds, which are seconds since 1970 (UTC).

© 2014 Microsoft - Terms of Use - Privacy