curl --request POST \
--url https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": {
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"idempotency_key": "<string>",
"status": "queued"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {
"manual_scans_remaining": 0,
"scan_credits": 0
}
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"idempotency_key": "<string>"
}
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}Request a scan
Request an on-demand visibility scan for an own brand.
Queues a scan through the same rule the dashboard’s manual scan uses: one scan queued or running per brand at a time (the check covers scans dated today, UTC; once it has finished, a new request the same day is accepted and uses the allowance again), the organisation’s manual-scan allowance and purchased scan credits apply (unlimited tiers skip the check), and the scan runs as a manual scan. Completion is observable through the brand’s last_scan_changed_at (and, for platform partners, the scan.completed webhook). Own brands only (400 validation_error with details[].code = "own_brand_only"). The brand’s state is re-checked at the moment it is queued: an archived brand is 409 brand_archived and a brand with no active prompt is 409 no_active_prompts — either way nothing is queued and no credit is touched (a queued job for such a brand would reserve a manual-scan credit and then run nothing).
curl --request POST \
--url https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.surfais.com/v1/orgs/{orgId}/brands/{brandId}/scan-requests', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));{
"data": {
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"idempotency_key": "<string>",
"status": "queued"
}
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {
"manual_scans_remaining": 0,
"scan_credits": 0
}
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": {
"job_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"idempotency_key": "<string>"
}
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}{
"error": {
"code": "<string>",
"message": "<string>",
"details": "<unknown>"
},
"request_id": "<string>"
}Authorizations
A Surfais API key — an organisation key or a partner key. Issued by Surfais; shown once. Rotate by creating a new key, then revoking the old one.
Headers
Optional. 1–255 characters, unique per intended write. Same key + same request → the stored response is replayed with Idempotent-Replayed: true; same key + different request → 409 idempotency_key_reuse; still running → 409 idempotency_key_in_flight. Outside that range → 400 validation_error (invalid_header).
1 - 255Path Parameters
Organisation id. Org keys: the key's own org. Partner keys: any org with an active link. Anything else is 404.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$Brand id within the org (own brand or competitor row); 404 when not in the org. Writes that add tracking need an own (400 validation_error with details[].code = "own_brand_only"), non-archived (409 brand_archived) brand.
^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$Response
Queued.
Show child attributes
Show child attributes