Back to posts
AINews

Mechanical Turk closes September 30: find the SageMaker jobs that run on it, and export the labels it will not keep

Amazon Mechanical Turk shuts on September 30, and the closure also removes the Mechanical Turk worker type from SageMaker Ground Truth and Amazon Augmented AI. Teams that never opened mturk.com can depend on it through one workteam ARN. How to find that ARN in your accounts and code, why an A2I flow definition has to be rebuilt instead of edited, a tested export script for direct Requesters, and the assignments that auto-approve before October 30.

Amazon Mechanical Turk closes on September 30, 2026, six days from today. The closure FAQ on mturk.com covers the Requesters who post HITs directly, which is expected. It also contains this answer:

This closure also applies to SageMaker Ground Truth and Amazon Augmented AI. The Amazon Mechanical Turk Worker type will no longer be available when creating labeling jobs or human review workflows as of September 30, 2026.

That second group often does not know it is on Mechanical Turk. A Ground Truth labeling job or an Augmented AI (A2I) review workflow bills through SageMaker, needs no Mechanical Turk account, and names its workforce with a single ARN that someone chose once, possibly in a notebook two years ago. If a Textract or Rekognition call in your product sends low-confidence results to human review, the review may be running on Mechanical Turk today.

This post covers both groups: how to find the dependency, what has to be rebuilt, what to export, and which dates in the FAQ do not protect your data.

The dates, and what each one covers

From the FAQ, verified today:

DateWhat happens
September 30HIT submission closes. Unsubmitted HITs expire, and you are not charged for them. The Mechanical Turk worker type disappears from new Ground Truth labeling jobs and A2I human review workflows.
October 30Last day to approve or reject work and to pay bonuses. Anything still pending is auto-approved.
Within 30 daysPrepaid Requester balances are refunded.
January 28, 2027Transaction history stops being available.

The last row is easy to misread as "my data is safe until January." Transaction history is the billing record. Your results, meaning the assignment answers that are the actual labels, fall under a different answer on the same page: "MTurk will remove HITs and corresponding Assignment data from your account after 120 days." That rule predates the closure. Two things follow from it. Anything older than about four months is already gone unless you saved it at the time. And nothing in the FAQ says how long the list operations will keep answering after September 30, so the export belongs in this week, not in the October review window.

Find the dependency: one ARN

Every labeling job and review workflow that uses Mechanical Turk names the same workteam, per the SageMaker workforce docs:

arn:aws:sagemaker:<region>:394669845002:workteam/public-crowd/default

394669845002 is the account number in the ARN AWS publishes for the public crowd, and it appears in nothing else, so it is a clean string to grep for. Start with code, because that is where the next job will come from:

grep -rn --exclude-dir=node_modules -e "public-crowd" -e "394669845002" .

Run it across your infrastructure repos (Terraform, CDK, CloudFormation), your notebooks, and any pipeline that creates labeling jobs. A hit in a template means the next job created from it fails after September 30.

Then check the accounts. Labeling job summaries carry the workteam ARN directly. Flow definitions do not appear in the list response with their workteam, so each one needs a describe call. The script below walks every region SageMaker is offered in and skips the ones your account has not enabled:

import boto3
from botocore.exceptions import ClientError, EndpointConnectionError

MTURK = "workteam/public-crowd/default"
session = boto3.session.Session()

for region in session.get_available_regions("sagemaker"):
    sm = session.client("sagemaker", region_name=region)
    try:
        for page in sm.get_paginator("list_labeling_jobs").paginate():
            for job in page["LabelingJobSummaryList"]:
                if MTURK in job["WorkteamArn"]:
                    print(region, "labeling-job", job["LabelingJobName"], job["LabelingJobStatus"])
        for page in sm.get_paginator("list_flow_definitions").paginate():
            for fd in page["FlowDefinitionSummaries"]:
                desc = sm.describe_flow_definition(FlowDefinitionName=fd["FlowDefinitionName"])
                if MTURK in desc["HumanLoopConfig"]["WorkteamArn"]:
                    print(region, "flow-definition", fd["FlowDefinitionName"], fd["FlowDefinitionStatus"])
    except (ClientError, EndpointConnectionError) as e:
        print(region, "skipped:", type(e).__name__)

It needs sagemaker:ListLabelingJobs, sagemaker:ListFlowDefinitions and sagemaker:DescribeFlowDefinition. Run it once per AWS account that does ML work. A line starting flow-definition with status Active is the one to act on first, because that workflow is wired into a live call path.

For a single region from the shell, the labeling-job half is one command:

aws sagemaker list-labeling-jobs --region us-east-1 \
  --query "LabelingJobSummaryList[?contains(WorkteamArn, 'public-crowd')].[LabelingJobName,LabelingJobStatus]" \
  --output table

A flow definition cannot be edited, only replaced

The SageMaker API has CreateFlowDefinition, DescribeFlowDefinition and DeleteFlowDefinition, and no update operation. The workteam ARN is fixed when the workflow is created. Moving an A2I workflow off Mechanical Turk therefore takes two steps, and the second one is the one teams miss:

  1. Create a new flow definition with the same task UI and output location, a new WorkteamArn, and without PublicWorkforceTaskPrice, which the API reference describes as "the price that you pay for each task performed by an Amazon Mechanical Turk worker."
  2. Repoint every caller at the new ARN. Three places pass a FlowDefinitionArn: Textract AnalyzeDocument and Rekognition DetectModerationLabels, both inside HumanLoopConfig, and the A2I runtime's StartHumanLoop. Grep your application code for FlowDefinitionArn; each hit either reads the ARN from config or hardcodes the old one.

Labeling jobs have no update operation either, but a labeling job is a one-shot run, so the fix is in whatever creates it: change the ARN in the template and drop the price field. The FAQ does not say what happens to a job or human loop that is still in flight on September 30. Finish it or stop it before then.

The two replacement workforces, both from the SageMaker docs:

  • Private workforce. People you choose, signed in through Amazon Cognito or your own OIDC identity provider. Each AWS account gets one private workforce per region, with as many work teams inside it as you need, and the same workforce serves Ground Truth and A2I. You recruit and pay these people yourself; SageMaker only routes the tasks.
  • Vendor workforce. A labeling company subscribed through AWS Marketplace, from the console only (Labeling workforces or Human review workforces → Vendor). After subscribing, ListSubscribedWorkteams returns the ARN to use. Price, schedule and refund terms sit in the subscription agreement with the vendor.

One constraint lifts in the move. The Mechanical Turk workforce could not be used on data containing personal information, which is why those jobs required the FreeOfPersonallyIdentifiableInformation flag. A private team of your own staff can see data your old jobs had to scrub. A vendor can too, but the docs put the compliance check on you: read the vendor's security practices and EULA before sending anything sensitive.

Direct Requesters: export this week

If you post HITs through the Requester API or website, this is the full export. It writes one JSONL file per object type and needs only credentials for the AWS account linked to your Requester account. The Requester API lives in us-east-1.

import json, boto3

mturk = boto3.client("mturk", region_name="us-east-1")

def pages(op, key, **kw):
    for page in mturk.get_paginator(op).paginate(**kw):
        yield from page[key]

def dump(name, rows):
    n = 0
    with open(f"{name}.jsonl", "w") as f:
        for row in rows:
            f.write(json.dumps(row, default=str) + "\n")
            n += 1
    print(f"{name}: {n}")

hits = list(pages("list_hits", "HITs"))
dump("hits", hits)

dump("assignments", (
    a for h in hits
    for a in pages("list_assignments_for_hit", "Assignments", HITId=h["HITId"],
                   AssignmentStatuses=["Submitted", "Approved", "Rejected"])
))

dump("bonuses", (
    b for h in hits for b in pages("list_bonus_payments", "BonusPayments", HITId=h["HITId"])
))

quals = list(pages("list_qualification_types", "QualificationTypes",
                   MustBeRequestable=False, MustBeOwnedByCaller=True))
dump("qualification_types", quals)

dump("qualification_workers", (
    w for q in quals for status in ("Granted", "Revoked")
    for w in pages("list_workers_with_qualification_type", "Qualifications",
                   QualificationTypeId=q["QualificationTypeId"], Status=status)
))

dump("worker_blocks", pages("list_worker_blocks", "WorkerBlocks"))

Three details if you adapt it. The paginator wants the snake_case operation name (list_hits); passing "ListHITs" raises a KeyError before any request is sent. ListAssignmentsForHIT returns only the statuses you ask for, so all three are listed explicitly. And the Answer field on each assignment is a QuestionFormAnswers XML string, stored as-is; parse it after the export rather than during it, so a parser bug cannot cost you the window.

The script makes two API calls per HIT, one for assignments and one for bonuses, plus pagination. On an account with many HITs that is a long run, which is one more reason to start it now rather than on the 29th.

Some assignments approve themselves before October 30

October 30 is the latest date. It is not the only one. Every submitted assignment carries an AutoApprovalTime, derived from the auto-approval delay you set on the HIT, and after that moment it counts as approved and you pay the reward plus fees. With a three-day delay, work submitted on the last day approves itself long before the October deadline.

The export already has the column. List what is still unreviewed, earliest deadline first:

jq -r 'select(.AssignmentStatus == "Submitted") | [.AutoApprovalTime, .HITId, .WorkerId] | @tsv' \
  assignments.jsonl | sort

Review from the top. Rejection still requires good cause under Mechanical Turk's policy, and after September 30 no worker can submit a replacement answer, so this list is also your last look at the labels you will be building on.

What moves to the next platform, and what does not

Worker IDs are Mechanical Turk identifiers. The pool of workers you qualified over months does not transfer to Prolific, a labeling vendor, or a private team. What does transfer sits in qualification_types.jsonl: each qualification type you own is exported with its Test and AnswerKey. That is a screening test with known answers, and it is useful in two places.

  • Screening on the new platform. Whatever you move to, run new annotators through the same test before they see production data, so the bar stays where it was.
  • Calibrating an LLM judge. If some of your HIT volume was scoring or classification that a model might now do, the exported labels are the test. Take items that had three or more human answers. Measure how often the humans agreed with each other, then how often the model agrees with the human majority. If the model agrees with the majority less often than the humans agreed among themselves, it is not a replacement for that task yet. Build the sample from the items humans disagreed on, because easy items make every judge look good. The evaluation guide covers building the set, and the failures a confident model hides are the reason not to skip the comparison.

The rest of the routing follows the task. Research studies and surveys with human participants fit a participant platform such as Prolific. Production annotation at volume fits a managed labeling vendor, or the Ground Truth vendor workforce if you want to stay inside SageMaker. Preference and red-team judgments for model training, the human half of RLHF, depend on annotators you can brief and re-brief as the guidelines change, which points to a managed vendor or a private team over an open crowd.

The six days, in order

  1. Grep your code and IaC for public-crowd and 394669845002; run the finder script in each AWS account.
  2. For each active flow definition: create the replacement on a private or vendor workteam, repoint AnalyzeDocument, DetectModerationLabels and StartHumanLoop callers, and test one human loop end to end.
  3. Direct Requesters: run the export, check that the HIT and assignment counts match what you expect, and store the files somewhere that does not expire in 120 days.
  4. Sort unreviewed assignments by AutoApprovalTime and review from the top.
  5. Verify your payment information in the Requester portal. The FAQ names it as what keeps the refund of your prepaid balance on time.
  6. Download transaction history for accounting before January 28, 2027.

This is the same shape as the Imagen 4 shutdown: the date is public, and the part that breaks is one layer below where people look. It is also a date that no model deprecation page will ever list, which is why an expiry register needs more than model IDs.

Get the next post when it ships

One email on Sunday with the new post and a short list of what shipped that week — new guides, tool updates, and a couple of links worth reading.