Amazon Mechanical Turk PHP REST API

Author: CPKS

Date: 2011-10-28

1. Table of Contents

2. Introduction

This document complements the API documentation generated from the source code. For details about number and types of arguments, etc., please see that. Instead, this document seeks to introduce the PHP API using a task-oriented approach, with sample code to illustrate how to use the PHP objects implementing the AMT API. The Task Overview section shows the way I have analyzed the task tree and applied it to this conspectus of the API.

3. Installation

Before using the PHP API, you need to install it on your system. If you are using PHP 5.3, this is not too difficult, as there are no special dependencies.

You must create a file amt_keys.php in your include path. This must execute the class initialization function to set up your AMT access key ID and secret access key. Here is a sample amt_keys.php:

amt\request::init_class('YOUR_ACCESS_KEY_ID', 'YOUR_SECRET_ACCESS_KEY');

This file is required by amt_rest_api.php and will initialize the base amt\request class for you.

There is an optional third parameter to amt\request::init_class(). This is a boolean value which, if TRUE, will send all your subsequent requests to the Sandbox. Alternatively, you can call amt\request::set_sandbox_mode(). Once you have set sandbox mode, you cannot cancel it for the rest of the run of your script.

For further details, see the separate installation documentation.

4. Classes

Broadly, the classes making up the PHP API are in three categories: request classes, response classes and exceptions. A request object is an instance of a request class, and facilitates the set-up of a request to the AMT service. Some requests simply cause an action to occur (e.g. blocking a worker) and return no useful information. Others return simple data types (e.g. account balance). These request objects have an execute() function that will return the useful return value, if any.

Some requests, on the other hand, may return relatively complex datasets or lists of datasets. In this case, a response object becomes necessary. For the most part response objects are either simple data objects (all data members public) or are descendants of \ArrayObject (accessible by array keys or object properties). Large resultsets are iterable using a standard iterator such as \ArrayIterator - these classes implement \IteratorAggregate. Resultset elements may be simple types, e.g. strings, or they may be response objects of a more complex kind. I am not documenting the aggregates here, just the objects they contain.

Finally, there are the exception classes. All errors are handled through exceptions. Invalid arguments result in (no surprises here) \InvalidArgumentException and other \LogicExceptions being thrown. Calling execute() without setting up required parameters, calling setup functions twice and similar misdemeanours result in \BadMethodCallException. I/o errors (e.g. response timeout) result in \RuntimeException. Finally, any error returned by the AMT service will result in an amt\Exception - which extends \RuntimeException.

5. Task Overview

5.1. Handling errors

This will involve embedding your API calls in a try block and handling exceptions in corresponding catch blocks. This API has been designed so that, if you keep tabs on the things your application needs to know, you should not need to catch exceptions. Further detail appears below.

5.2. Setting things up

Before getting any useful results from the AMT, it will be necessary to set up the general task parameters you intend to use - in AMTese, a HITType. If you intend to require your workers to take a qualification test, you will also need to create a QualificationType, which is then attached to the HITType.

5.3. Creating a task

When you create a HIT, you refer to the HITType for many of the details; others are task-specific.

5.4. Receiving notifications

While your HIT is running, you can optionally receive notifications from the AMT service that will tell your application what is going on. These may trigger queries.

5.5. Running queries

The AMT service supports numerous queries, some of which are essential for the most basic operation, e.g. retrieving the results of the workers' assignments. If you use qualification tests, you may need to poll the service occasionally to get workers' qualification requests.

5.5.1. Paged queries

Some queries solicit lists of results, some of which might be quite rich in properties. Depending on your use of the service, these lists might be quite big. Therefore, the AMT server frequently delivers chunked or "paged" responses. In these cases, the AMT API allows you to select a number of common options:

sort property
The property on which to sort, e.g. time, HITId
sort direction
Ascending or Descending
page size
Default is 10. If you're expecting thousands of results, it might be wise to up this a bit.
page number
You don't need to worry about this: the PHP API insulates you from the sordid details of paging.

If you don't need to customize these properties, you don't need to use the request classes provided in the PHP API. Instead, you can simply use response objects which act as aggregates of whatever type you're interested in.

5.6. Responding to completed tasks

Once you have retrieved details of workers' activity, your application may need to react: in the first place, simply to store their answers, but also maybe to approve, reward, disapprove or even block a particular worker. These actions are typically conducted in the context of the appropriate response object.

6. Handling errors

If you receive an exception of type \LogicException (typically \InvalidArgumentException, \LengthException, \OutOfRangeException), it may be due to inattentive reading of Amazon's API documentation. The PHP API implemented herein tries to check for all invalid arguments before you get as far as the AMT server. You should not expect to receive an exception in this class once your code is beyond the testing stage.

Exceptions of type \BadMethodCallException will be due to misunderstandings of the PHP API wrapping. Examples might include repeated calls to set the parameters on a request object intended for once-only use. Again, you should not expect to receive an exception in this class once your code is beyond the testing stage.

6.1. Handling runtime errors

Exceptions of type \RuntimeException are thrown when external resources are defective, responses from the AMT service are not understood by the PHP API, or an error is reported by the AMT service. In the latter case, an amt\Exception is thrown. amt\Exception extends \RuntimeException with methods to return the XML response from the AMT service, and to dump this response to file or to the console. For example, suppose that we try to get the details of a non-existent HIT:

try {
  $r = new amt\results('ThisIsAnInvalidHitId');
catch (amt\Exception $e) {
  error_log($e->getMessage() . $e->xmldata());

When an AMT error occurs, it is theoretically possible for multiple error items to be returned. These are parsed into the exception message as follows:

error_code: error_message

Each code/message pair is separated by a newline. NB the codes are strings, not integers.

(An amt\Exception may also be thrown if the XML structure of the response is unexpected. In this case, there will be no colon in the message. If this happens, it is due to an error in the PHP API and is my fault, not yours.)

7. Setting up a task: the HITType

The HITType defines the following basic parameters of your task:

An amount (float) in US dollars
HIT Title
Description of the HIT
No. of seconds allowed for a worker to complete the HIT once he has accepted it
Optionally, comma-separated list of search words
No. of seconds before the HIT is automatically approved, or FALSE to disable this feature
qualification requirements
Optionally, a set of qualification requirements: a worker must have all of these in order to be able to accept (or, if you so decide, even view) the HIT.

The request object that does the work here is amt\hittype_request. There are two ways to supply the parameters listed above. One is to call the set_params() method; the other is to read the settings from a configuration file. Here we see an example of direct set-up:

$r = new amt\hittype_request;
$r->set_params(0.05, 'General Knowledge Test',
 'This test includes ten fairly simple questions to test your general knowledge.',
 80, 'general knowledge', 3600);
$hittype_id = $r->execute();

This is an example of the nearly-ubiquitous execute() method returning a simple type, here a string. The HITTypeId is a unique identifier which your application should ideally manage. If you call the same function twice with exactly the same parameters, it is not an error: you will simply get returned the same HITTypeId. Incidentally, if you want to dispense with a HITType, there is no API call to do so. Instead, it will linger in some lonely wardrobe on the AMT server like a pensioner's wedding dress.

The second way to set up a HITType is to use a configuration file. Here is an example configuration file:

## HIT Properties

title:General Knowledge Test
description:This test includes ten fairly simple questions to test your general knowledge.
keywords:general knowledge

## HIT Timing Properties

# this HIT Lifetime value is 60*5 = 5 minutes

# this Auto Approval period is 60*60*12 = 12 hours

Note that the keys are case-insensitive.

Here is an example of the use of a configuration file:

$r = new amt\hittype_request;
// code to prepare array $qr of amt\qualification_requirements...

If you choose to take the configuration file route, you will need to have amt_conf.php on your include path. Otherwise, you don't need it.

Further information about setting qualification requirements is contained in the separate Qualifications section.

8. Creating a HIT or task

According to the underlying API, a HIT can be created in four ways: with or without a HITType, and with an internal or external definition. HITs without a HITType have all their HITType parameters specified directly. External HITs are specified using the URL of a task page located on the caller's server, whereas internal HITs are specified in full using a subset of HTML and are located on the AMT web site.

Currently, this API caters only for HITs with a HITType specified, and only for external HITs.

8.1. General: HIT status

However a HIT is created, it will be available to be accepted by a worker throughout its lifetime.

A HIT may have any one of the following status:

The HIT has not expired and fewer than the maximum assignments (worker acceptances) have taken place. In this state, the HIT is viewable and acceptable by new workers.
The HIT has not expired, but the maximum assignments (worker acceptances) have occurred. Therefore, the HIT results are not yet all ready to be received, but the HIT will not be available to new workers.
When a HIT is in this state, it is possible to query the workers' answers. Either the HIT has expired, or the maximum number of assignments has completed. In either case, the HIT is not available to new workers. If the maximum number of assignments has not completed, the HIT must have expired and can be revived by extending its lifetime. If its lifetime has not expired, all assignments must have completed and it may be revived by increasing its maximum number of assignments.
This is a special status requested by the requester. It can only be assigned to a HIT when the HIT is in the Reviewable state. It is used to filter HIT query operations and is designed to allow multiple requester processes to analyze reviewable HITs concurrently.
The requester has asked for the HIT to be disposed, i.e. deleted. When a HIT is in this state, it can be regarded as being in the mortuary department. It remains only for the AMT equivalent of street sweepers or grave robbers to toss its defunct body into the canal, and it will no longer be visible to the requester or to anyone else.

8.2. External HITs with HITType

To specify an external HIT, it is necessary to develop a static or dynamic page containing a web form and locate it in a publicly accessible area of an open web site. When the HIT is created, one need specify only the URL of this page, which is then relayed to the AMT worker by the AMT server. The advantages of this form of hit are that:

That said, a good number of parameters must be specified when the individual HIT is created. These are:

The HITTypeId of the HIT, as returned by the call to amt\hittype_request::execute().
The URL of the page, hosted on a caller's web site, that contains the HIT form.
An arbitrary string, used by the requester to identify the HIT; can be blank.
An integer value representing the number of seconds for which the HIT will remain available to AMT workers. After this number of seconds has elapsed, the HIT becomes "expired". Once the HIT is expired, it will remain active for workers who have already accepted the task and are still working on it, but will not be made available to anyone else.
An integer value representing the number of workers who will be allowed to work on the HIT before it is made unavailable to others.
The height in pixels of the sub-frame within the AMT-hosted window in which the HIT page appears. This value need not be specified and defaults to 640.

All of the above parameters are arguments to the constructor of the request object, which is an instance of amt\external_hit_request. Here is an example of HIT creation:

$hit_type = get_hittype(); // somehow acquire the HITType ID of the HIT
$job_id = get_job_id();    // somehow identify the specific task
$url = 'http://my_site/hit_page.php?job_id=' . $job_id;
$lifetime = 5 * 60;        // The task is expired after 5 minutes
$job_qty = 5;              // We want 5 workers to have a go at this
$r = new amt\external_hit_request($hit_type, $url, $job_id, $lifetime, $job_qty);
$hit_id = $r->execute();   // after calling this, the HIT is 'assignable'

Your application will probably need to cache the HITId returned by this operation in order to handle the workers' responses.

If this operation successfully completes (i.e., throws no exception), then the HIT will be available to AMT workers.

9. Receiving Notifications

Optionally, you can set up a notification handler on your server to receive notifications from the AMT service when things happen to your HITs. There are a number of classes to simplify this task.

9.1. Setting up Notifications

There are two request classes that will set up notifications. The first is a test notification request, which you may wish to use to see whether your handling script is sane. Executing an amt\test_notification_request will cause the AMT server to respond immediately with a notification to your handler. The second is the real live notification request, which you use on the HITType of your HITs to tell the AMT server to notify you of certain events.

9.1.1. Testing Notifications

Here is how to fire a test notification:

$a = array('HITReviewable', 'HITExpired');
define('URL', 'http://url_of_my_handler.php');
$r = new amt\test_notification_request('Ping', URL, $a);

The first parameter of the amt\test_notification_request is the test operation. The 'Ping' operation is a do-nothing operation which merely tests the availability of your handler script. To test the functionality of your handler more fully, you might substitute 'HITReviewable' or 'HITExpired' here.

9.1.2. Setting up Notifications for real

Once you have verified that your notification handler is in place and is sane, you activate notifications for your HITs by attaching a notification specification to their HITType(s). This is done using an instance of amt\hittype_notification_request. You need to specify what kinds of notifications you want: these are specified as an array of notification type names. Sample code:

$a = array('HITReviewable', 'HITExpired');
define('URL', 'http://url_of_my_handler.php');
$r = new amt\hittype_notification_request($hittype_id, URL, $a);

9.2. Handling Notifications

When the AMT server fetches your notification handler URL, it passes the notification(s) as GET parameters. All the hard work of parsing them is done by amt\notification_response, whose acquire() method returns an iterator over a set of amt\notification objects. These objects have the following public members:

The HITId of the HIT in question
The event that triggered this particular notification
The HITTypeId of the HITType of this HIT
The time of the notification (string)
The time of the notification (int)
The ID of the assignment - this will be present if you are receiving notifications (say) each time a worker submits your HIT, using the AssignmentSubmitted event type.

Here is a sample notification handler script, one which is designed to handle all the responses to a HIT after it has expired or become reviewable:

/* AMT notification reception */
require 'amt_rest_api.php';
require 'my_pdo_database.php';

try {
  $DB = new my_pdo_database;
  $hits_treated = array(); // prevent handling the same HIT twice

  $notifications = amt\notification_response::acquire();
  // prepare a query to store the worker's answer in the database
  $s = $DB->prepare('CALL amt_record_answer(?,?,?,?,?,?)');
  foreach ($notifications as $n) {
    if (in_array($n->hit_id, $hits_treated)) continue; // already done
    $s->bindValue(1, $n->hit_id);
    $hits_treated[] = $n->hit_id; // prevent this HIT being handled again
    $results = new amt\results($n->hit_id); // get the responses
    // record result answers in archive
    foreach ($results as $result) {
      $result->approve(); // approve the assignment - worker gets paid
      $s->bindValue(2, $result->accept_time, PDO::PARAM_INT);
      $s->bindValue(3, $result->submit_time, PDO::PARAM_INT);
      $s->bindValue(4, $result['WorkerId']);
      $s->bindValue(5, $result['AssignmentId']);
      $s->bindValue(6, $result['answerField']);
    // now let's delete the HIT
    $hit = new amt\minimal_hit($n->hit_id);
catch (Exception $e) {

Note that we do everything we can within a try block: the AMT server expects a 200 header and we don't want PHP errors to foul this up: the AMT service doesn't need to know if our script falls over.

As a point of interest, the amt\notification_response object is necessarily a single, global object (there is only one possible set of GET parameters). Rather than using a public constructor, therefore, which would allow the construction of a plurality of objects, we use the static class method acquire() to do the job.

10. Running Queries

Before we consider responding to completed tasks, which itself involves running certain queries, we turn to some general-purpose queries that may be found useful.

10.1. Account balance

This is the simplest query: "How much money is left in my account?"

We use the amt\balance_request class; since there is little point in instantiating objects - after all, we just want to know how much - the static execute() method of the class returns the amount as a string in the form '$9.99'. Example:

$balance = amt\balance_request::execute();
echo "Balance is $balance\n";

10.2. Hit Types

After you've created a few HIT types, you might think it would be useful to query the AMT service to get a list of them. Sorry, nothing doing. You must keep tabs on them yourself. Note that if you re-register a HIT type with exactly the same data as before, you will retrieve the HITTypeId previously created.

If your application repeatedly uses only one or two HIT types, it would seem to make sense not to re-register them every time you want to create a HIT.

10.3. Search All HITs

When looking up our HITs, the most general way is to find them all. This query introduces us to the paged query, one of the most complex query types. If, however, you don't want to tamper with the default settings, then all you need to do is to create an amt\hitlist object. This will give you an iterable collection of amt\hit. For example, to list your HITs and their HITTypes:

echo "HIT ID: HIT Type IDn\";
foreach (new amt\hitlist as $hit) echo "$hit->HITId: $hit->HITTypeId\n";

If, however, you want to adjust the sort order or sort attribute, you can do it by first setting up your request object and specifying all the parameters you want to customize, thus:

$r = new amt\search_hits_request;
$r->set_sort('HITTypeId', 'Ascending');
echo "HIT Type ID: HIT ID\n";
foreach (new amt\hitlist($r) as $hit) echo "$hit->HITTypeId: $hit->HITId\n";

The amt\hitlist object has one potentially useful function: its write_csv() function can be called to dump HIT data to a named file. If the file is newly-created, a first row of column headers is written automatically.

10.4. Search reviewable and/or reviewing HITs

You may want to poll for your reviewable HITs (i.e. those whose assignments are now complete). As well as selecting from your HITs according to their Status, this differs from the standard HIT search in that the only data returned for each HIT is the HITId. Effectively this means that what is received from this call is a collection of strings. However, these strings are wrapped in PHP objects (amt\minimal_hit) that offer significant functionality. For details, see the next section, Responding to Completed HITs.

If you don't want to tamper with the default selection, sorting and paging settings, you need only use an amt\reviewable_hitlist, as follows:

$r = new amt\reviewable_hitlist;
foreach ($r as $mhit) {
  foreach ($mhit->results() as $result) {
    // deal with the results

To customize the sort order or page size of the result set, you use the amt\reviewable_hits_request object explicitly. For example:

$r = new amt\reviewable_hits_request;
$r = new amt\reviewable_hitlist($r);

In the AMT API, it is possible to select HITs with status 'Reviewing' using substantially the same code. This is done by including the status in the constructor to the request object, as follows:

$r = new amt\reviewable_hits_request('Reviewing');
$r = new amt\reviewable_hitlist($r);

It is also possible to use this API to include HITs with either 'Reviewing' or 'Reviewable' status. This is achieved using the pseudo-status 'Both' as argument to the constructor of the request object.

It is not an error to supply the default argument, 'Reviewable', to the request object.

amt\reviewable_hitlist inherits amt\hitlist::write_csv().

10.5. Getting HIT details

If you are looking at your reviewing or reviewable HITs, you will have noted that all these searches give you is the HITId. You may need more data about the HIT. If you need HITDetail and HITAssignmentSummary information, all you need do is create an amt\hit_details object from the HITId as follows:

$hit = new amt\hit_details($hit_id); // this fetches fuller details of the HIT

Just what data you receive depends upon the "response groups" you elect to receive here. These can be selected by using a request object and setting them. The available response groups are:

Just the HIT Id - which presumably you already have.
Includes the HIT's question data (an XML bundle returned as a string).
Includes most of the other things you might want to know about a HIT, including its creation time, its AutoApprovalDelayInSeconds, etc.
Includes numbers of assignments pending, available and completed. (This figure seems not to include assignments already approved!)

By default, amt\hit (and the request object it uses) will fetch the HITDetail and HITAssignmentSummary groups - Minimal being unnecessary. If you wish to modify this behaviour, you use an instance of amt\get_hit_request, as follows:

$r = new amt\get_hit_request($hit_id);
// The following would be equivalent:
//$r->set_response_groups(array('HITQuestion', 'HITDetail'));
$hit = $r->execute(); // returns amt\hit

The response classes amt\hit and amt\hit_details are both extensions of \ArrayObject. The properties of their instances can be iterated over like an associative array, or they can be accessed as object properties, as you prefer. These properties are documented in the Amazon reference, and are spelled the same way.

10.6. Bonus payments

You can retrieve collections of bonus payments, either by HIT or by individual assignment. (Multiple payments per individual assignment are possible.) This is a paged collection; the Amazon documentation is however silent on whether sort options are available. My advice: don't push your luck. Twiddling the page size is however possible; as usual the default is 10 results per fetch. You could bump this up if you expect to receive of the order of hundreds.

The objects in the collection are of type amt\bonus, which extends \ArrayObject. All its properties are accessible either via array index or as object properties. They are:

The ID of the worker in question
The ID of the assignment for which the bonus was paid
A float value, being the amount of bonus paid in US dollars
A string timestamp
An integer UNIX-style timestamp, derived from GrantTime
The reason (string) specified when the bonus was paid

You control the filtering of the selection using the constructor arguments of the request object, which is an instance of amt\get_bonus_request. If the second parameter is FALSE or omitted, it's for a HIT, otherwise just for a single assignment and the ID provided is understood as an assignment ID. Example:

$r = new amt\get_bonus_request($hit_id);
$payments = $r->execute();
$total = 0.0;
foreach ($payments as $b) $total += $b->BonusAmount;
echo "Total bonus payout for $hit_id was $total\n";

10.7. Retrieving Statistics

The AMT service makes some statistics available to you: Amazon documents the names of these statistics and their meanings.

As well as specifying the statistic you want, you must specify the time period, which is one of OneDay, SevenDays, ThirtyDays or LifeToDate. In the case of OneDay only, you can specify a count n > 1, which will return you the statistic for the last n days. (In the case of the NumberHITSAssignable statistic, you must specify LifeToDate or you will get an \InvalidArgumentException. This reflects a limitation in the AMT API.) Here is an example:

$s = amt\stats_request::execute('NumberHITsReturned', 'OneDay', 5); // last 5 days
foreach ($s as $t => $v) {
  $times[] = $t; // integer timestamp
  $values[] = $v; // integer in this case

In most cases it is permissible only to retrieve one statistic (for the SevenDays, ThirtyDays or LifeToDate periods). Yet the result is always returned as an array, with just one member. The following is neater and to be preferred:

$n_completed = amt\stats_request::exec1('NumberHITsCompleted', 'ThirtyDays');

The single value returned by amt\stats_request::exec1() and the array values returned by amt\stats_request::execute() are integers if counts, else floats. Where the array contains multiple OneDay results, the array keys are integer timestamps as returned by time(), and the array is sorted by ascending order of key.

This covers all the queries except for Qualifications, which will be found in a separate section below, and Assignments, which we turn to next.

11. Responding to Completed HITs

Once your HIT has been completed and entered the reviewable state, you will either receive a notification or you will poll for results (or perhaps both). You can poll for results using the amt\reviewable_hitlist search. The most obvious thing you will want to do is to get the answers from your workers.

11.1. Getting workers' answers

The simplest way to do this, which requires only that you have the ID of the HIT, is to use an amt\results collection. This is an iterable collection of instances of amt\assignment, and these in turn are what contain your workers' answers. Suppose that your HIT form has an entry field called 'Answer1': then the following will collect into the $answers array all your workers' answers for the specified HITId:

$results = new amt\results($hit_id);
foreach ($results as $asst)
  $answers[$asst['AssignmentId']] = $asst['Answer1'];

The amt\assignment object is an \ArrayObject and all your answer fields will be available as indices. The corresponding values will typically be strings. In addition, the following standard indices may be present (see also the Amazon documentation for Assignment):

Id of the assignment - which may subsequently be used with certain request functions
Id of the worker who accepted the HIT
the ID of the HIT, in case you didn't already know
Status of the assignment: one of Submitted/Approved/Rejected; it will be absent if the worker has not yet submitted the HIT
The time at which the assignment will automatically be approved - absent if the worker has not yet submitted the HIT
The date and time the HIT was accepted
The date and time the worker submitted the HIT - absent if not yet submitted
The date and time the requester approved the assignment - absent if not yet approved
The date and time the requester rejected the assignment - absent if not yet
The date and time by which the assignment will be considered abandoned if not already submitted
The feedback provided by the requester, if any. NB this is not yet supported by the PHP API.

In addition, the amt\assignment may contain the following public data members as object properties:

ID of the assignment
Time the assignment was accepted, as a UNIX-style integer timestamp
Time the assignment was submitted, as a UNIX-style integer timestamp

If you want finer control over the paging and sorting of the amt\results collection, you can use an instance of amt\results_request. In addition to the normal paging/sorting parameters, you can specify the sort property as SubmitTime (the default), AcceptTime or AssignmentStatus. You can also select on the basis of AssignmentStatus: the default is to select only Submitted assignments. The following example selects only Approved assignments:

$r = new amt\results_request($hit_id);
$r->set_sort('SubmitTime', 'Descending');
$results = $r->execute();

One potentially useful feature of amt\results is that it can dump a .csv file to disk. As a precaution against accidental mingling of data, the pathname provided has the HITId appended (or, if the pathname is a directory name, the filename will be HITId.csv). Here is how to use it:


The file csvwriter.php is part of this package. If you don't intend to use the csv feature, you can ignore this file. It is included only if the csv functions are called.

11.1.1. Uploaded file answers

When evaluating the answers, there is one special consideration, namely, the situation where an answer is an uploaded file. Here, the corresponding answer field will contain not a string (as normal for an answer) but an array containing two elements: the size of the file, and a unique key. The size may be of interest, but the key is useless. I cannot understand its function. But it's there in case you want to print it out and frame it. The array is associative and might look like this: array('size' => '12002', 'filekey' => 'zzbb6q?'). To retrieve this file, you have to call another API using amt\file_upload_url_request. This is achieved as follows:

$url = $assignment->get_upload_url($question_name);

If you don't have the amt\assignment handy, then you can do this:

$url = amt\file_upload_url_request::execute($asst_id, $question_name);

You have then 60 seconds to perform the download using the URL.

11.2. Responding to submitted assignments

Once you have received an amt\assignment, you can elect to do various things to it: approve or reject it, give the worker a bonus or even block him from ever touching your HITs again. Some of these things are conveniently handled by the amt\assignment class.

11.2.1. Paying the worker

To approve all the assignments for a HIT, one method would be:

$results = new amt\results($hit_id);
foreach ($results as $asst) $asst->approve('Well done!');

The worker feedback parameter is optional here, but mandatory for reject(), as also for grant_bonus().

Note that if you wish to approve all the assignments, you can simply do this:

$results = new amt\results($hit_id);
$results->approve_all(); // could have given feedback, but didn't

11.2.2. Paying the worker a bonus

This is as simple as:

$assignment->grant_bonus(0.35, 'Great answer!');

If you decide to grant a bonus later, but don't have an amt\assignment handy, then provided you have the assignment and worker IDs on file you can grant the bonus like this:

amt\grant_bonus_request::execute($worker_id, $asst_id, 0.35, 'Great answer!');

11.2.3. Rejecting the answer

Given an amt\assignment, all you have to do is:

$assignment->reject('You obviously weren\'t trying');

11.2.4. Blocking the worker

Given an amt\assignment, all you have to do is:

$assignment->block_worker('Time-waster; does not even try to get it right');

If you don't have an amt\assignment handy, you will have to do this instead:

amt\block_worker_request::execute($worker_id, 'Stupid answers!');

Once you have blocked a worker, he falls off your radar. The AMT API provides a way to reverse the process, but no way either to query blocked workers or to ascertain whether a worker has already been blocked or not.

Nota Bene: the reason parameter is said in the Amazon documentation to help you "keep track of your workers". However, I can find no API to extract this information from the AMT system. This necessary documentation is not shown to the worker, so it may be only for the benefit of the AMT admins.

11.2.5. Sending workers a message

You can send a message to a selection of workers. You make the selection and provide the worker IDs as an array of strings. Assuming $workers below is such an array, and $msg is the message to send, this is how to do it:

amt\notify_workers_request::execute($msg, $workers);

11.2.6. Reversing a block

Given an amt\assignment, all you have to do is:

$assignment->unblock_worker('oops! made a mistake');

If you don't have an amt\assignment handy, you will have to do this instead:


Note that you need not supply a reason for this operation: it defaults NULL.

11.3. Tinkering with HITs

While you are at the reviewing stage, you are not confined to dealing with the workers and their answers. You can also tinker with the HIT itself. In the first place, you might want to get rid of it, and you can do this at any time. You have to be careful how you do it, however.

11.3.1. Early HIT deletion

If the HIT is assignable or unassignable, i.e. not yet reviewable, this API will cause all currently accepted tasks to be automatically approved and paid; workers won't be able to accept the HIT after this is called. When all assignments are terminated, the HIT and all assignment data will be destroyed. This is rightly likened to a "stop button". However, once the HIT becomes reviewable, this API will not work. (Don't blame me: Amazon make the rules.) See the next section for what to do instead.

The easiest way to stop the HIT is to create an amt\minimal_hit object and call its disable() method:

$h = new amt\minimal_hit($hit_id);

This method will also work with amt\hit_details, if you have one to hand (it also implements amt\hit_i).

If you want to save money at the expense of a little more processing, you might consider expiring the HIT instead.

11.3.2. Late HIT deletion

If the HIT has become reviewable, we switch tactics from abortion to euthanasia. First, all assignments must be approved or rejected. This may be swiftly accomplished using amt\results::approve_all().

Then we use the dispose() method of e.g. amt\minimal_hit:

$h = new amt\minimal_hit($hit_id);

11.3.3. Expiring a HIT

If you want to stop workers accepting your HIT, the cheapest way is to force it to expire. Workers who have already accepted the HIT will, if they submit it, still need to be paid and you will need to approve or reject their efforts. Again, one can use the expire() method of e.g. amt\minimal_hit:

$h = new amt\minimal_hit($hit_id);

11.3.4. Changing the HITType

After creating a HIT that needs workers to be qualified, you may find that there are no qualified workers available, and you are in a hurry for a result. Rather than destroying the HIT and creating another, you can switch horses in midstream, and throw the HIT open, by using this API. This can be done with any HIT class, e.g. amt\minimal_hit:

$h = new amt\minimal_hit($hit_id);

11.3.5. Extending a HIT

A HIT may be extended in two ways: by extending its lifetime, and by increasing its maximum number of assignments. Both of these extensions involve the specification of an increment value. These functions are available only for amt\hit and amt\hit_details:

$h = new amt\hit_details($hit_id);
$h->extend_assignments(20); // increase max assignments by 20
$h->extend_expiry(3600);    // add 1 hour to expiry time

11.3.6. Reviewing a HIT

While your application is reviewing a hit in the reviewable state, your application logic might trigger a separate process to begin doing the same. In order to prevent this from happening, you can try to set its status to reviewing. This should prevent your other processes finding the HIT when they search for reviewable ones. N.B. You should always try this operation, because if there is a race to review the same HIT, the second process to attempt the status switch should get an error from the AMT service and if so an amt\Exception will be thrown. Here is how to use this feature:

$hits = new amt\reviewable_hitlist; // these will be amt\minimal_hit s
                                    // any amt\hit_i will do
foreach ($hits as $hit) {
  try {
    $hit->set_reviewing();          // might throw
  catch (amtException $e) {
    continue;                   // OK, someone else got here first, just skip
  // process the HIT here...

12. Qualifications

The qualifications API concerns four entities:

Attributes of workers; they refer to qualification types.
qualification types
Qualification specifications: these may include a test with or without answer key.
qualification requests
When a worker completes a qualification test, he sends you a qualification request.
qualification requirements
You specify these for your HIT types in order to restrict them to qualified workers.

Qualifications and their types themselves fall into three categories:

score qualifications
These have a numeric score attached. A typical use would be to count number of rejected HITs, or a score in a qualification test.
locale qualification
Each worker has exactly one of these. It represents his location. Locale qualifications have a country code as a string value.
predicate qualifications
These qualifications have no value: either they exist (have been awarded) or they don't.

These three categories emerge clearly when we consider qualification requirements, which are specified differently for each.

The workflow with qualifications can be rather lengthy:

  1. Design the test if necessary, and any answer key if appropriate
  2. Create the Qualification Type (this is where any test is specified to AMT)
  3. Enable the Qualification Type if necessary - then workers can take the test
  4. Poll to see if any workers have taken the test
  5. Check the workers' answers and calculate a score
  6. Award the qualification as a score to the worker
  7. Create HITTypes with Qualification Requirements

It is for this reason that the qualification-related API is such a sizable proportion of the whole, and has been separated off from the rest of the API.

I am not offering any help with step 1: see the Amazon documents on QuestionForm and AnswerKey. But please note this warning: the AMT API allows file uploads in any QuestionForm. However, they should not be used in a qualification test, since the AMT API allows download of the file only in the context of a HIT assignment. If you use one in the PHP API, and an answer is submitted, a warning will be generated. There is no documented way to download files uploaded during a qualification test.

12.1. Qualification Type Setup

The relevant class is amt\create_qualification_request. The constructor arguments define the common parameters:

The name of the qualification
A description of not more than 2000 characters
TRUE if it is desired to make the qualification available immediately (default TRUE)
Comma-separated string of search terms, not more than 1000 characters (optional, but needs to be got right from the beginning: it cannot be changed later)

Method calls set the remaining parameters: different sets will suit different types of qualification, depending on whether they are scored, automatically granted, etc.

12.1.1. Setting up with test

If your workers are to take a test, you must generate an XML file containing the QuestionForm according to the AMT specification and schema. To set up the qualification, this is what you do:

$r = new amt\create_qualification_request($name, $desc, TRUE, $keywords);
$r->set_test('path_to/my_test.xml', 60 * 20); // allow 20 minutes
// specify other options here
$qualtype_id = $r->execute();

If you want the AMT system to mark the answers automatically, then you should also generate a file with the answer key XML in it. Then you can do this:

$r = new amt\create_qualification_request($name, $desc, TRUE, $keywords);
$r->set_test('path_to/my_test.xml', 60 * 20); // allow 20 minutes
$r->set_answers('path_to/my_answers.xml'); // AMT will do the marking
// specify other options here
$qualtype_id = $r->execute();

12.1.2. Allowing retakes

If you want to allow retakes, you must specify a delay in seconds before a worker will be allowed to re-take the test. Here is how:

$r = new amt\create_qualification_request($name, $desc, TRUE, $keywords);
$r->set_test('path_to/my_test.xml', 60 * 20); // allow 20 minutes
$r->set_answers('path_to/my_answers.xml'); // AMT will do the marking
$r->set_retry_delay(7 * 24 * 60 * 60); // allow retry after a week
$qualtype_id = $r->execute();

Unless you call set_retry_delay(), workers will not be allowed to re-take the test.

12.1.3. Auto-granted qualifications

As an alternative to setting a test, some types of qualification might be awarded to any worker that requests them. Here is how to set one of these up:

$r = new amt\create_qualification_request($name, $desc, TRUE, $keywords);
$r->set_auto(1); // this could be a 'parameter type' qualification
$qualtype_id = $r->execute();

The integer parameter to set_auto() must be a positive integer; if zero, it disables auto-grant. This is legal: you may wish to award the qualification manually or according to some performance metric of your own devising.

12.2. Altering a QualificationType

This is much the same as creating a qualification type in the first place. The difference is that the constructor of the request object takes the qualification type ID as returned by the previous operation. All the option calls of amt\create_qualification_request are available, and also set_desc() to set a new description, and set_active() to enable/disable the qualification type. If you don't set a retry delay, then even if there was one before, there won't be one after the update. If you specify a new test or test duration but no answer key, the old answer key is dropped. If you don't specify either a test or an answer key, both are preserved. You cannot remove a test if the qualification type was created with one. Here, therefore, is an alteration that simply sets the qualification inactive (and disables retakes, so they will remain disabled if the qualification type is reactivated):

$r = new amt\update_qualification_request($qualtype_id);
$qualtype_id = $r->execute();

12.3. Polling for qualification requests

This involves the use of the unfortnately-named amt\qualification_requests_request. This receives a paged response, so the usual page size and sort options are possible. You may sort either on SubmitTime or on QualificationTypeId. Note that the request object can either select a single qualification type (if specified to the constructor) or aggregate the requests of all your qualification types. The result of the request is a collection of instances of amt\qual_rq. One of these includes the following public data:

The qualification type ID
The ID of the worker who is requesting the qualification
The (string) timestamp at which the request was made
An array of answers, indexed by answer names, which can be one of
  1. A string, in the case of a free text or selection type answer
  2. An array('answer' => 'string', 'otherAnswer' => 'string')
  3. An array('size' => int_filesize, 'filekey' => 'stringkey') - warning there is no documented method of retrieving this file. A warning will be issued by trigger_error if one of these is encountered.

To iterate over all the qualification requests for a specific qualification type, one might do something like this:

$r = new amt\qualification_requests_request($qt_id); // just one qual. type
$requests = $r->execute();
foreach ($requests as $req) { /* do something useful here */ }

See the next section for sample code to poll for and score all outstanding qualification requests.

12.4. Scoring qualification tests

amt\qual_rq does contain a qualification request ID; but unusually for a member datum of a response object, it is private. (We can only hope and pray that the designer knew what he was doing.) The only way to grant or refuse a qualification, therefore, is to use the grant() and reject() member functions of the amt\qual_rq. Here is an example of its use:

$r = new amt\qualification_requests_request; // cover all qualification types
$requests = $r->execute(); // get the collection of amt\qual_rq
foreach ($requests as $req) {
  $score = my_score($req->qual_id, $req->answers); // mark the answers
  if ($score < 1)
    $req->reject("You scored only $score!");

12.5. Restricting access to HITs

It is possible to attach qualification requirements directly to a HIT. The PHP API does not currently support this.

It is possible to attach qualification requirements to a HITType. This is supported through the set_qual_requirements() method of amt\hittype_request. This method expects as argument an array of amt\qualification_requirement instances. You cannot create these directly (the constructor is protected). Instead you must create an instance of one of the three subclasses corresponding to the three qualification categories. Once you have generated your array, you can register the HITType like this:

$qual_requirements = create_my_qual_requirements(); // set them up somehow
$r = new amt\hittype_request;
$r->set_params(0.05, 'General Knowledge Test',
 'This test includes ten fairly simple questions to test your general knowledge.',
 80, 'general knowledge', 3600);
$hittype_id = $r->execute();

Creation of the elements of $qual_requirements is considered immediately below.

12.6. Creating qualification requirements

You cannot create qualification requirement objects directly: instead, you create instances of one of the three sub-classes. These correspond to the three categories of requirement, which are (in order of decreasing complexity): score, locale and predicate. Almost everything about a qualification requirement is specified in the constructor. The only option is whether you want to prevent unqualified workers from even seeing the HIT. You can achieve this by calling set_required_for_preview() on the requirement object before using it.

12.6.1. Creating a score qualification requirement

These are created as instances of amt\score_requirement. The constructor takes three arguments:

The ID of the qualification type we're talking about (i.e. the test whose score we're interested in)
A comparison operator, which must be one of:
The (integer) score to compare to.

So, here we go:

$req[0] = new amt\score_requirement($qualtype_id, 'GreaterThan', 29);

For illustrative purposes, we have stored the requirement in an array and we have stipulated that the HITs will not be visible to workers who have not achieved a score of 30 or more. (I confess I was tempted to make the comparison operator 'NotEqualTo' above. It's rather like when you're standing at the edge of a cliff, the temptation to jump...)

12.6.2. Creating a locale qualification requirement

These are created as instances of amt\locale_requirement. The constructor takes two arguments:

A comparison operator, which must be one of:
A string, being an ISO 3166 country code, e.g. 'US' or 'FI'

So, to require French citizens for a HIT type, we could specify:

$req[] = new amt\locale_requirement('EqualTo', 'FR');

12.6.3. Creating a predicate qualification requirement

These are created as instances of amt\predicate_requirement. The constructor expects just the QualificationTypeId requirement:

$req[] = new amt\predicate_requirement($qualtype_id2);

12.7. Using qualification requirements

After using the above three examples, the $req array will be ready for use to apply three qualification requirements to a HIT Type as follows:

$r = new amt\hittype_request;
$r->set_params(0.05, 'General Knowledge Test',
 'This test includes ten fairly simple questions to test your general knowledge.',
 80, 'general knowledge', 3600);
$r->set_qual_requirements($req); // add qualification requirements
$hittype_id = $r->execute();

Note that you cannot modify a HIT Type, e.g. by adding qualification requirements to it. Consider the following example:

$r = new amt\hittype_request;
$r->set_params(0.05, 'General Knowledge Test',
 'This test includes ten fairly simple questions to test your general knowledge.',
 80, 'general knowledge', 3600);
$hittype_id1 = $r->execute();
$hittype_id2 = $r->execute(); // this will be the same as $hittype_id1
$r->set_qual_requirements($req); // add qualification requirements
$hittype_id3 = $r->execute(); // this will be different from $hittype_id1
$hittype_id4 = $r->execute(); // this will be the same as $hittype_id3
$req2 = array($req[2]); // this is a single amt\predicate_requirement
try {
  $hittype_id5 = $r->execute(); // we won't get here...
catch (BadMethodCallException $e) {
  echo "You cannot call set_qual_requirements twice on the same object!\n";
  echo 'Exception message: ' . $e->getMessage() . PHP_EOL;

The above code will register just two HIT types. It will then display the exception message. If you wish to apply different sets of qualification requirements to otherwise similar HIT types, this is how:

$r = new amt\hittype_request;
$r->set_params(0.05, 'General Knowledge Test',
 'This test includes ten fairly simple questions to test your general knowledge.',
 80, 'general knowledge', 3600);
$hittype_id1 = $r->execute(); // HIT Type with no qualification requirement
$r2 = clone $r; // duplicate the request object
$r->set_qual_requirements($req); // add qualification requirements
$hittype_id2 = $r->execute(); // this will have 3 qualification requirements
$req2 = array($req[2]); // this is a single amt\predicate_requirement
$r2->sqt_qual_requirements($req2); // this is fine
$hittype_id3 = $r->execute(); // this will have 1 qualification requirement

This last example creates 3 hit types.

We have now covered all the essentials of using qualifications. There are, however, a number of additional facilities that I shall consider for the sake of completeness.

12.8. Assigning a qualification directly

It is possible to assign a qualification to a worker directly, without their having to do anything to earn it. This is achieved as follows:

$workers = find_my_good_workers(); // build an array of workers to assign to
$qt_id = 'SAMPLESAMPLE'; // imagine this is a valid qualification type ID
$r = new amt\assign_qualification_request($qt_id); // set up request object
foreach ($workers as $worker_id) $r->execute($worker_id, TRUE);

The execute() method of amt\assign_qualification_request can be called repeatedly with different parameters, these being Worker IDs and an optional boolean value that, if specified and true, prevents the worker being sent a notification.

12.9. Revoking a qualification

Yes, it is possible to undo a qualification assignment:

amt\revoke_qualification_request::execute($qt_id, $worker_id, 'Assigned in error');

The worker will receive a notification if the third parameter is specified. It might be justifiable to omit it, however, if the qualification was assigned without a notification.

12.10. Obtaining qualification scores

It is possible to query the scores of your workers (in the example, IDs in array $workers) for a given qualification type $qt_id, like this:

$rq = new amt\qualification_score_request($qt_id);
foreach ($workers as $w_id) $scores[$w_id] = $rq->execute($w_id);

12.11. Adjusting qualification score

Given the qualification type ID and the worker's ID, it is possible to adjust the qualification score like this:

amt\update_qualification_score::execute($qualtype_id, $worker_id, 100);

12.12. Searching Qualification Types

AMT provides the facility to look up qualification types based on a search string. It is also possible to filter out qualification types that you did not create.

The request object, amt\search_qualtype_request, obtains a paged collection, so it supports the usual paging options. The AMT documentation states that the sort order is Descending by default, which is unusual. The only valid sort key is Name. The constructor of the request object takes the following arguments:

a boolean value: if FALSE, it will include non-requestable (system) qualification types such as no. of HITs completed; if TRUE, it will filter them out.
a boolean value: if TRUE, it will search only the qualification types you have created.
an optional search string to match against keywords (and, it is implied, other fields - name perhaps?). It is possible to re-execute the request with a different search string by calling the set_search() method of the request object.

Here is how to get a collection of all your qualification types:

$rq = new amt\search_qualtype_request(TRUE, TRUE);
$qualtypes = $rq->execute();
// now narrow the search
$qualtypes2 = $rq->execute();

The collection returned by the execute() method can be treated as an array of instances of amt\qualtype. These objects are accessible either as associative arrays or their properties may be accessed using object-style syntax, e.g. $qt->Keywords and $qt['Keywords'] are equivalent.

12.13. Inspecting a Qualification Type

It is possible to inspect the details of a single qualification type if you know its ID.

$qual = amt\get_qualification_request::execute($qual_id);

Here, the returned value $qual is our friend the amt\qualtype.

12.14. Searching Qualifications

It is possible to look up all the qualifications of a specified type (provided, that is, you created the qualification type in the first place: otherwise, you will get an amt\Exception if you try).

The request object, amt\qualifications_for_type_request, obtains a paged collection, so it supports the usual paging options. Sorting options are undefined. There are two arguments required to create a request:

The ID of the qualification type whose qualifications you're interested in
Granted or Revoked - defaults to Granted (need not be specified if so)

Here is how to obtain a collection of qualifications:

$r = new amt\qualifications_for_type_request($qualtype_id);
$quals = $r->execute();

The collection returned by the execute() method can be treated as an array of instances of amt\qual. This is an object with public data members grant_time, status, value and worker_id.

12.15. Searching Qualified HITs

You can get a list of all your HITs that use the given qualification type.

The request object, amt\hits_for_qual_request, obtains a paged collection, so it supports the usual paging options. Sorting options are undefined. The only constructor argument is the qualification type ID:

$r = new amt\hits_for_qual_request($qualtype_id);
$hits = $r->execute();

The collection returned by the execute() method can be treated as an array of instances of amt\hit.

12.16. Deleting qualification types

If you delete a qualification type, you also delete any HIT types that use it. Just specify the ID of the qualification type as follows:


13. API Index

As stated at the outset, this document takes a task-oriented approach to documenting the API. However, it may be useful (particularly if you are familiar with the AMT API) to have an alphabetical list of AMT APIs and the PHP equivalents. The left-hand column gives the AMT APIs with a link to Amazon's documentation. The central column gives the PHP API classes. The right-hand column gives a link to the documentation in this document.

AMT API PHP class(es) Covered here
ApproveAssignment amt\assignment Pay the worker
AssignQualification amt\assign_qualification_request Assigning a qualification directly
BlockWorker amt\assignment Blocking the worker
ChangeHITTypeOfHIT amt\hit_i Changing the HITType
CreateHIT amt\external_hit_request Create external HIT
CreateQualificationType amt\create_qualification_request Qualification Type Setup
DisableHIT amt\hit_i Early HIT deletion
DisposeHIT amt\hit_i Late HIT deletion
DisposeQualificationType amt\dispose_qualification_request Deleting qualification types
ExtendHIT amt\hit
Extending a HIT
ForceExpireHIT amt\hit_i Expiring a HIT
GetAccountBalance amt\balance_request Account balance
GetAssignmentsForHIT amt\results
Getting workers' answers
GetBonusPayments amt\bonus
Bonus Payments
GetFileUploadURL amt\file_upload_url_request Uploaded file answers
GetHIT amt\get_hit_request
Getting HIT details
GetHITsForQualificationType amt\hit
Searching qualified HITs
GetQualificationsForQualificationType amt\qual
Searching qualifications
GetQualificationRequests amt\qualification_requests_request
Polling for QualificationRequests
GetQualificationScore amt\qualification_score_request Obtaining qualification scores
GetQualificationType amt\get_qualification_request
GetRequesterStatistic amt\stats_request Retrieving Statistics
GetReviewableHITs amt\reviewable_hits_request
Search reviewable HITs
GrantBonus amt\assignment Paying the worker a bonus
GrantQualification amt\qual_rq Scoring Qualification Tests
Help N/A Not implemented
NotifyWorkers amt\notify_workers_request Sending messages to workers
RegisterHITType amt\hittype_request Create a HITType
RejectAssignment amt\assignment Rejecting the answer
RejectQualificationRequest amt\qual_rq Scoring Qualification Tests
RevokeQualification amt\revoke_qualification_request Revoking Qualifications
SearchHITs amt\search_hits_request
Search all HITs
SearchQualificationTypes amt\qualtype
Searching qualification types
SendTestEventNotification amt\test_notification_request Testing Notifications
SetHITAsReviewing amt\hit_i Reviewing a HIT
SetHITTypeNotification amt\hittype_notification_request Setting up Notifications
UnblockWorker amt\assignment Reversing a block
UpdateQualificationScore amt\update_qualification_score Adjusting Qualification Score
UpdateQualificationType amt\update_qualification_request Altering a QualificationType

14. Not Implemented

Feature Excuse
HITs without a HITType Amazon will create a HIT type from the property values anyway, so it is neater just to register the HIT type and save time thereafter.
Internal HITs It is easier to specify and test external HITs, and the HITType creation process is easier too.
requester feedback in GetAssignmentsForHIT It should be possible to obtain this by specifying a response group of AssignmentFeedback to the requester object. However, the default response group is not documented and I don't know whether specifying this one will prevent all the other information from coming through.
Help I was going to skip this one, then I thought O Heck, why not just do it too, it might be fun to play with. So I wrote a full implementation of a Help request object and found that AMT doesn't actually provide any help at all, it just refers you to an URL. So I have removed my implementation: it is a waste of space, as is the corresponding AMT API.