Watching this list from an agent
Updated 17 Oct 2026
Three things up front, because they decide everything else. There is no account, no API key and no webhook: polling is the only mechanism, and it will stay that way until there is a reason to build more. The list behind the feed is refreshed roughly every six hours. And a listing is a pointer, not a contract — the work is claimed on the platform that published it.
The feed
One address, no query parameters, no credentials:
GET https://goatmarket.com/offers.json
It returns the same rows as the list on the front page, in the same order. The fields worth building on:
url— the listing on the platform that published it. That is where a claim is made.seite— the row's page on this site. Stable across crawls; use it as the identity of a job.betrag_text— the amount word for word, as the platform wrote it.betrag_usd— the number derived from it. Can benull, and is never a substitute for the text.betrag_art—max(a ceiling),pool(to be divided),fixed(a flat figure), or null.art—bug_bounty,audit_contest,competitionoross_bounty.geprueft_am— when the platform's page was last read.
Poll it
One request, filtered to what is worth waking up for:
curl -s https://goatmarket.com/offers.json \
| jq -r '.auftraege[]
| select(.betrag_usd != null and .betrag_usd >= 5000)
| [.betrag_usd, .plattform, .titel] | @tsv'
A loop that only reports what it has not seen before — the state file matters more than the filtering, because an agent that reports the same job every hour gets muted, and a muted feed is a feed nobody reads:
import json, pathlib, urllib.request
FEED = "https://goatmarket.com/offers.json"
SEEN = pathlib.Path("seen.json")
seen = set(json.loads(SEEN.read_text())) if SEEN.exists() else set()
req = urllib.request.Request(FEED, headers={
"User-Agent": "my-agents-watcher/1.0 (+https://example.com/about)",
})
daten = json.load(urllib.request.urlopen(req, timeout=30))
neu = []
for a in daten["auftraege"]:
if a["seite"] in seen:
continue
if a["betrag_usd"] is None or a["betrag_usd"] < 5000:
continue
neu.append(a)
for a in neu:
print(f'{a["betrag_usd"]:>9,} {a["plattform"]:<12} {a["titel"][:70]} {a["url"]}')
seen.update(a["seite"] for a in daten["auftraege"])
SEEN.write_text(json.dumps(sorted(seen)))
How often to poll
The upstream platforms publish new work a handful of times a day, so once an hour is already
generous and once a minute only costs both sides something. Read the stand field
first: if it has not moved since your last call, there is nothing to do. We publish no rate
limit because there is no hidden one — a reasonable poller never meets it.
Deciding what to take
- Sort by
betrag_usd, then readbetrag_textbefore committing. "Up to $150,000" and "$150,000" are different sentences, and the first one is not a promise. betrag_arttells you which sentence you are reading.poolmeans several teams share it;maxmeans the top of a range.- A row with
betrag_usd: nullis still work. It simply does not carry a number we can prove, and we will not invent one. fristis empty more often than not. That means the platform stated no deadline, not that there is none.- Check the platform's own scope and eligibility on
urlbefore your agent spends an hour. Most disappointments happen there, not in the work.
Claiming the work
Nothing on this site can be claimed on this site. Every row links out to the platform that published it, and the account, the rules, the review and the payout are theirs. If a listing needs a human identity, a company or a verification step, that is between you and them — and worth discovering before the agent starts rather than after.
Being a good citizen
- Identify yourself: a user agent with a reachable contact, not a browser string.
- Cache the response; do not re-fetch per row.
- Do not point the same loop at the upstream platforms. Their robots rules are their own, and they rate-limit by address.
- If you pass a listing on, keep the platform's name and link with it. The work is theirs.
What is not here
No webhook, no push notifications, no accounts, no API keys, no commission. If a listing disappears from the feed it is because the platform stopped publishing it, not because we withdrew it — the page stays reachable, marked as closed, which is the honest way to keep a reference you may already have saved.