Submission API
The v2 Submission API queues a URL for analysis in urlquery's isolated browser and reports the analysis progress. When the analysis is done, read the result with the Report API.
All routes are on https://api.urlquery.net and require x-apikey.
| Method | Path | Quota class | Result |
|---|---|---|---|
POST | /public/v2/submissions | submission_create | Queue a URL. Returns the submission, including the future report_id. |
GET | /public/v2/submissions/{queue_id} | submission_lookup | Progress of a submission made by you or your team. |
Every call also consumes the account's overall v2 quota, so the overall quota can limit you before the submission quota does.
Before you submit
- Search first. An existing report may already answer the question without visiting the URL again. See report search.
- Submit only URLs you are authorized to analyze, and remove tokens, personal data, and internal names first. See safe use.
- Choose visibility on purpose.
publicis the default. See choosing visibility.
Submit a URL
POST /public/v2/submissions
curl --fail-with-body --request POST \
--header "x-apikey: ${URLQUERY_API_KEY}" \
--header 'content-type: application/json' \
--data '{"url":"https://login.example.com/verify","access":"restricted","run_time":60,"tags":["case_1234"]}' \
https://api.urlquery.net/public/v2/submissions
The body is one JSON object of at most 64 KiB. Unknown fields are rejected.
| Field | Required | Values | Default | Description |
|---|---|---|---|---|
url | yes | Absolute http or https URL, up to 8192 characters | — | The page to open. A URL without a scheme is rejected rather than guessed. |
access | no | public, restricted, private | public | Who can see the report. It also sets how long the report is kept; see data retention. |
run_time | no | 20 to 200 | 26 | Seconds the browser stays on the page. Increase it for pages that wait before redirecting or loading content. |
useragent | no | Up to 512 characters | A current desktop Firefox | The browser identity the page sees. |
device_type | no | desktop, mobile, tablet | desktop | The device type reported to the page. |
tags | no | Up to 20, each 1–64 letters, digits, or _ | none | Your own labels, stored as submit.tags. Search for them later, for example submit.tags:case_1234. |
meta | no | Up to 20 string pairs; keys up to 64 characters, values up to 1024 | none | Your own context, such as a case ID. Visible only to you and your team. Never put secrets here. |
sanitize_url | no | true, false | false | Replaces a base64-encoded email address at the end of the URL path with a placeholder address. It does not remove other personal data. |
The response is the submission with status set to queued:
{
"success": true,
"data": {
"queue_id": "0d3f8a8e-2b7c-4f41-9a37-5b8f0c6d7e21",
"report_id": "6f1c1a52-8d4e-4f0e-b1a1-1c2d3e4f5a6b",
"status": "queued",
"url": {"schema": "https", "addr": "login.example.com/verify", "fqdn": "login.example.com", "domain": "example.com", "tld": "com"},
"ip": {"addr": "203.0.113.10", "port": 443, "asn": 64500, "as": "EXAMPLE-AS", "country": "Norway", "country_code": "NO"},
"access": "restricted",
"run_time": 60,
"device_type": "desktop",
"useragent": "Mozilla/5.0 …",
"sanitize_url": false,
"tags": ["case_1234"],
"meta": {}
}
}
Save both IDs. queue_id follows the analysis; report_id is the report it will produce.
Which URLs are accepted
The gateway checks the target before queuing it:
- The host must resolve, and every address it resolves to must be public. Private, loopback, link-local, carrier-grade NAT, multicast, reserved, and documentation ranges are rejected, as are IP-literal URLs in those ranges.
.onionhosts are accepted without resolution.- Some domains are not accepted for analysis. They return
400with the messagethe URL cannot be submitted.
| Message | Cause |
|---|---|
url must be an absolute http or https URL | Missing scheme, another scheme, or no host. |
domain does not resolve | The host has no DNS answer. Check the spelling, or retry later if it is newly registered. |
url resolves to a non-public address | At least one address is not on the public internet. |
the URL cannot be submitted | The analysis service refused the URL, for example a blocked domain. |
Follow a submission
GET /public/v2/submissions/{queue_id}
curl --fail-with-body \
--header "x-apikey: ${URLQUERY_API_KEY}" \
"https://api.urlquery.net/public/v2/submissions/${QUEUE_ID}"
Only the account that submitted, or a member of its team, can read a submission. Any other queue_id returns 404 NOT_FOUND.
status | Meaning |
|---|---|
queued | Waiting for an analysis browser. |
processing | The browser is visiting the URL. |
analyzing | The visit is over and detections are running. |
done | The report is ready. Read it by report_id. |
failed | The analysis did not produce a report. Submitting again later can succeed. |
A normal analysis takes about 30 seconds plus run_time, depending on load. Poll every 5 to 10 seconds; each poll counts against submission_lookup.
import time
submission = call("POST", "/submissions", json={"url": "https://login.example.com/verify", "access": "restricted"})
while submission["status"] not in ("done", "failed"):
time.sleep(10)
submission = call("GET", f"/submissions/{submission['queue_id']}")
if submission["status"] == "done":
overview = call("GET", f"/reports/{submission['report_id']}/overview")
call is the helper from the API workflows.
The submission object
| Field | Description |
|---|---|
queue_id | The submission ID. |
report_id | The ID the finished report will have. The report can be read once status is done. |
status | queued, processing, analyzing, done, or failed. |
url | The URL as it will be analyzed, split into schema, addr, fqdn, domain, and tld. |
ip | The address the target resolved to when queued, with ASN and country. 0.0.0.0 for .onion hosts. |
access, run_time, device_type, useragent, sanitize_url | The settings the analysis uses, with defaults filled in. |
tags, meta | Your labels and context, as stored. |
Retries and duplicates
POST /submissions is not idempotent: each successful call queues a new analysis and consumes quota.
- Do not retry a submit automatically after a timeout or a
502/504. The analysis may already be queued. Tag your submissions, wait for the analysis time to pass, and search for the tag (submit.tags:case_1234) or list your recent reports (scope=user) before submitting again. - Retry after
429onceRetry-Afterhas passed, and after503; those requests were not admitted. - Polling with
GET /submissions/{queue_id}is safe to retry.
Errors
| Status | Error | Cause |
|---|---|---|
400 | BAD_REQUEST | Invalid body or field, or a URL that cannot be analyzed. message says which. |
401 | MISSING_API_KEY | No x-apikey header. |
403 | INVALID_API_KEY | The key is invalid or disabled. |
403 | FORBIDDEN | The analysis service refused the key. |
404 | NOT_FOUND | The submission does not exist or belongs to another account. |
429 | RATE_LIMIT_EXCEEDED | The submission or overall quota is used up. Wait Retry-After seconds. |
502, 504 | BAD_GATEWAY, GATEWAY_TIMEOUT | The analysis service failed or timed out. For a submit, check whether it was queued before retrying. |
503 | RATE_LIMIT_UNAVAILABLE | Quota enforcement is temporarily unavailable. Retry later. |