Search Cameo's talent marketplace by name and get structured rows: video, DM, rush, and business price tiers, category tags, star rating, booking activity, and availability, pulled from Cameo's own search API. Built for talent-booking agencies and marketing competitive-intel teams.
Fixes the UNDER_MAINTENANCE flag from Apify's own QA run
(Q7wsjG34leg5KvCc2, build 0.0.3): the Actor died 5s into its 300s
budget on invalid JSON: Expecting value: line 1 column 1 (char 0).
Root cause: src/client.py treated an HTTP 200 response that
failed .json() as an immediate, non-retryable failure — indexed
against RETRY_STATUS_CODES (which only covers 408/429/5xx), a 200
status short-circuited straight to a dead end with zero retries and
zero rotation. Live recon on 2026-09-30 (plain curl-cffi, direct AND
through a rotating datacenter proxy, all four impersonation profiles)
confirmed POST /api/v3/search/users itself is healthy and returns
well-formed JSON every time — the target has NOT moved or changed
shape. The account's Apify Proxy is independently invoice-blocked
account-wide as of this fix (GET /v2/users/me →
), which is the far more likely source of a
malformed/empty 200 reaching the Actor when it routes through Apify
Proxy — but the Actor had no way to tell "blocked/malformed" apart
from "route is gone" either way, so this fix does not assume which one
it is.
client.py: a 200 response that fails JSON parsing is now classified
SearchAttempt(malformed_response=True) instead of surfacing the raw
parse exception — logged with the HTTP status, Content-Type, and a
200-char body snippet (never assumed to be JSON on faith).
malformed_response is now retryable (previously only
408/429/5xx/network errors were), reusing the existing exponential
backoff (2s base, cap 30s, 5 attempts — same budget as before, still
well inside the 300s QA window).
Each retry after a malformed response rotates the curl-cffi
impersonation profile (round-robin across BROWSER_PROFILES,
distinct from the previous one) before the next attempt, matching
the fleet's challenge detected — rotating convention (see
quora-questions-scraper/src/client.py) that this Actor was
previously missing entirely.
A response that stays malformed/non-JSON after all 5 attempts still
fails that one query loud, with the status/content-type/snippet in
the log — it does not crash the run or silently return zero rows.
Verified locally: apify run against
tests/fixtures/input.qa.json now SUCCEEDS end-to-end (5 rows for
"Snoop Dogg", exit 0) — proxy creation itself fails fast under the
current invoice block and falls back to direct routing (existing
main.py behaviour, unchanged), then the direct request to Cameo
returns real data.
0.1.0 — 2026-08-24
Initial release. Implements REQ-1 through REQ-11 from
docs/specs/cameo-talent-listings-scraper/spec.md:
Searches Cameo's own JSON search backend (POST /api/v3/search/users,
Algolia-backed) per input query, no browser/cookies/auth required
(live-verified 2026-08-24).
Validates input before charging actor-start (REQ-1/REQ-2).
Paginates each query up to maxResultsPerQuery, stopping early once
the target is hit or the target page confirms no more rows (REQ-7).
Cross-query dedup by talent id, with matched_queries listing every
distinct input query that surfaced a talent and exactly one
result-row PPE charge per unique talent (REQ-8).
Converts all Cameo price fields from raw cents to 2dp USD floats,
preserving 0 vs null as distinct (REQ-9/REQ-10).
Retries on 408/429/5xx/network errors with exponential backoff
(2s base, cap 30s, 5 attempts); per-query fault isolation so one bad
query never aborts the run (REQ-5/REQ-6).
Falls back to the base prod_cameo_talent_v0 index when a
non-featuredsortBy index is rejected by Cameo (REQ-11).
Zero matched rows across a run that searched successfully is a
SUCCEEDED run with a status message, never a failure (REQ-3/REQ-4);
a run only fails when every query could not complete a search at all.