Prerequisites
You need three things:
-
A TestLookup installation you can throw away. If you don’t have one, follow the Quick start. Clone the code, run the quick-start command, and wait until the readiness check succeeds:
Terminal window make quickstartcurl -f http://localhost:8000/health/ready -
An account with the QA engineer role or higher. Both of TestLookup’s upload methods refuse anyone below that role, whatever key or sign-in they use. The refusal is HTTP 403. On a quick-start installation, choose Admin in the quick-login panel on the sign-in page.
-
Bash, a common command shell. On Windows, use Git Bash or WSL. The commands below don’t run in PowerShell as written.
The user workflow guide on GitHub describes the whole journey, from upload to a reviewed report. This page covers the first part: the upload, reading the file and the finished run.
Create a small test report
The command below creates a small JUnit report with one passing test and one failing test. That way, you’ll see both a saved result and a failure. Run this command, and the later ones, from the same folder:
cat > results.xml <<'XML'<?xml version="1.0" encoding="UTF-8"?><testsuite name="website-tutorial" tests="2" failures="1" errors="0" skipped="0" time="0.03"> <testcase classname="tutorial.Sample" name="passes" time="0.01" /> <testcase classname="tutorial.Sample" name="fails" time="0.02"> <failure message="expected true">example tutorial failure</failure> </testcase></testsuite>XMLUse made-up test data in this file. Don’t include production logs, passwords or keys, personal data or signed storage links.
Set up a test project and key
-
Create a project for this exercise. Open Settings › Project › Projects and choose New Project. Give it a name and a slug, which is a short name used in web addresses. You need to be a member of the project with the QA engineer role or higher. A key acts with its owner’s role, so a key that belongs to a viewer can’t send results.
-
Create a key for that project. Only an administrator can create a key that works for just one project. Open Settings › Project › Streaming API keys for the project, or use the keys API. TestLookup shows the key only once.
If you aren’t an administrator, a personal key from Settings › Account › My API keys also works for this tutorial. But a personal key can reach every project you belong to.
Don’t reuse an older key whose list of permissions is empty. Such keys can reach more than you expect.
Details: the API call is
POST /api/v1/keys, with theproject_idof your project. -
Set the values in your shell. Keep the key in the shell, or in a temporary file that Git ignores. Never put it in a file you commit:
Terminal window export TL_URL='http://localhost:8000'export TL_API_KEY='paste-your-key-here'curl --fail-with-body "$TL_URL/api/v1/projects" -H "X-API-Key: $TL_API_KEY"The reply lists the projects the key can see. Copy the ID of your test project:
Terminal window export TL_PROJECT='paste-the-project-id-here'
Write down the project ID, the key’s name, the start time and the TestLookup version you’re running, as a Git commit or image name. Then you can compare your results later.
Upload
Send the file with this upload command:
curl --fail-with-body "$TL_URL/api/v1/ingest/file" \ -H "X-API-Key: $TL_API_KEY" \ -F "project_id=$TL_PROJECT" \ -F 'build_number=website-tutorial-1' \ -F 'format=junit' \ -F 'file=@results.xml'You should get an accepted reply (HTTP 202) like the one below. Copy both IDs.
The task ID tracks the reading of this file. The run ID is the test run where TestLookup saves the results.
{"status":"accepted","run_id":"…","task_id":"…",…}Uploads can be up to 50 MiB, as documented. Compressed archives have extra limits of their own:
- how big the files get when unpacked;
- how many files the archive holds;
- how big each file is;
- how much bigger the files get compared with the archive, called the expansion ratio.
Those extra limits don’t raise the 50 MiB upload limit. See the upload guide on GitHub.
Check the task, then the run
Check the upload task first, then the run:
export TL_TASK_ID='paste-the-task_id-here'export TL_RUN_ID='paste-the-run_id-here'
curl --fail-with-body "$TL_URL/api/v1/ingest/uploads/$TL_TASK_ID" \ -H "X-API-Key: $TL_API_KEY"curl --fail-with-body "$TL_URL/api/v1/runs/$TL_RUN_ID" \ -H "X-API-Key: $TL_API_KEY"Repeat the first command until the task’s state is succeeded or failed. On the way, the task moves through pending, parsing and ingesting.
Parsing means reading the file, and ingesting means saving the results. Once the task has succeeded, the second command returns the run.
Until the results are saved, you may not be able to read the run yet. Wait a few seconds and try again.
TestLookup keeps the task record for 24 hours. So if the task check comes back “not found” (HTTP 404), the run may still exist.
The record may have expired, or failed to save, while the run was saved. The API guide on GitHub describes every request and reply.
Read the final status
Read the run’s status from the run itself. Don’t go by the number of results you sent, or by the accepted reply. When TestLookup marks the run as finished, it sets the status like this:
- FAILED if any test failed or broke.
- STOPPED if no test ran, or if the results are unknown and none failed.
- PASSED otherwise.
For this file, expect the task to reach succeeded, with both tests accepted: 1 passed and 1 failed. Expect the run to show status FAILED, 2 tests and a 50% pass rate.
If you see something else, write down the accepted and rejected counts, the final status and any analysis steps that were skipped.
What a recorded run showed
This run was recorded on September 21, 2026, with an earlier development version of TestLookup. That version came before the QA engineer role check was added to the upload methods, so details may differ on later versions.
A two-test file like the one above was uploaded to a throwaway project under Podman, an alternative to Docker. The upload was accepted (HTTP 202), with separate run and task IDs.
The task reached succeeded, with 2 results attempted, 0 rejected, 1 passed and 1 failed, and was marked complete. The run could be read, with status FAILED, 2 tests and a 50% pass rate.
Cleanup and common errors
When you’re done:
- Delete the report file you created.
- Delete the key under Settings › Project › Streaming API keys. For a personal key, use Settings › Account › My API keys instead. You can also delete a key through the API.
- Delete the test project if you no longer need it.
- On your own machine, stop TestLookup. Run the clean-up command only if you want to delete all of its data.
rm results.xmlmake stop # stop TestLookup and keep its datamake clean CONFIRM=yes # only if you want to delete all of its dataDetails: the API call to delete a key is DELETE /api/v1/keys/{key_id}.
Common errors:
| Reply | What it means | What to do |
|---|---|---|
| 401 | The key is missing, wrong or expired. | Check the key you set in your shell. |
| 403 “Requires at least QA_ENGINEER role to submit test results” | The key’s owner has a lower role. | Use a key that belongs to a QA engineer or higher. |
| 403 (other) | The key belongs to a different project, or you aren’t a member of this one. | Check the project ID and the project’s members. |
| 400 | TestLookup doesn’t support the format you named, or the file is empty. | Check the file and the format field. |
| 413 | The file or archive is over a size limit. | Use a smaller file. |
| 422 | A field is invalid, for example an empty build number. | Read the error detail. |
| 429 | The project has sent more uploads than its rate limit allows for now. | Wait for the time in the Retry-After header, then try again. |
| 503 | The server is overloaded or can’t store the upload. Or uploads in that format are switched off, which applies to Cypress and Playwright. | Wait for the time in Retry-After if it’s there, or ask an administrator. |
| Task failed after an accepted reply | TestLookup accepted the file but couldn’t read or save it. | Read the task’s error. |
| Task not found (404) | The task record expired or was never saved. | Check the run instead. |
If a problem keeps happening, check:
- the task’s error;
- file storage;
- the worker logs, and which queues the workers listen to;
- the run’s activity;
- the rejected results;
- anything waiting to retry, or set aside after failing again and again.
Next: connect a CI job, or review an AI report for the run you just created.