verification
use it
# add to stunt.yaml
services:
threads:
adapter: embedded:threads-style
# then
$ stunt upThe embedded: source extracts this adapter from the binary — no git clone, fully offline.
how to mock the Threads API (Meta) API locally
Install stunt, paste the service block above into stunt.yaml, run stunt up — then point your client at localhost instead of Threads API (Meta). It works offline, keeps state across requests and restarts, and every response is synthetic: the route shapes, status codes, pagination, and webhook delivery follow the real Threads API (Meta) v1.0 surface — the data is fake, the behavior is real.
backing stores
tokenscodescontainersmedia
api surface (8)
generated from the adapter manifest — exactly what boots, nothing more
8 routes
GET/oauth/authorize
POST/oauth/access_token
GET/v1.0/me
GET/v1.0/{container_id}
GET/v1.0/{id}/insights
POST/v1.0/{id}/threads_publish
POST/v1.0/{id}/threads
GET/v1.0/{id}/threads
VM-verified behaviors (39)
- ✓authorize without redirect_uri, state or client_id is invalid_request
- ✓authorize redirects back with a fresh code and the state echoed
- ✓a redirect_uri that already carries a query is joined with &
- ✓the exchange demands grant_type=authorization_code
- ✓an unknown code is 400 invalid_grant
- ✓a good exchange mints a token, user and refresh token
- ✓the code is single-use: a replay is invalid_grant
- ✓client mismatches are 400 invalid_client
- ✓a mismatched attempt leaves the code redeemable (house rule)
- ✓a second flow mints a distinct user
- ✓a missing bearer is 401 in the Graph error envelope
- ✓wrong schemes and unknown bearers answer the same 190
- ✓every API route enforces the same gate
- ✓a bearer dies at its clock-derived 60-day expiry
- ✓me returns the static mock profile
- ✓every user's token sees the same static profile (deviation, as-is)
- ✓?fields= is ignored: the full profile always comes back
- ✓a container without media_type=TEXT and text is a code-100 400
- ✓create assigns a c_* container id
- ✓a TEXT container polls finished immediately (no processing window)
- ✓back-to-back publish assigns an m_* media id
- ✓republishing the same container mints a second media (deviation, as-is)
- ✓simulate_fail ends in error and never publishes
- ✓unknown container ids answer the catch-all 404
- ✓the inbox lists the user's media newest first
- ✓each post carries one synthetic reply child
- ✓the inbox is scoped to the path user
- ✓?fields= projection is ignored (deviation, as-is)
- ✓all four manifest metrics return in canonical order
- ✓values are deterministic across reads
- ✓?metric= projects to the requested names in canonical order
- ✓an unknown metric name empties the data set (deviation, as-is)
- ✓insights for an unknown media id are the catch-all 404
- ✓refresh demands a known refresh_token
- ✓a refresh mints a fresh 60-day token bound to the same user
- ✓the response omits the rotated refresh_token (deviation, as-is)
- ✓the used refresh token is single-use: a replay is invalid_grant
- ✓the original access token keeps its own 60-day life
- ✓the refreshed token outlives the original's expiry
Not implemented (3)
- Only TEXT media containers; no image or video media types or uploads
- No reply or quote-post creation; inbox replies are synthetic
- No user-level insights; only 4 per-media metrics
Differs from the real API (6)
- Container processing is a fixed ~3s clock-derived window; text needs no real poll
- simulate_fail=true on container create is a simulator-only failure switch
- Refresh-grant response omits the rotated refresh_token; only the code exchange returns one
- /me is a static profile shared by every token; the path user is never validated against the bearer
- The refresh response omits the rotated refresh_token, so each token refreshes only once
- Republishing a container mints duplicate media
want more of the Threads API (Meta) surface? adapters are YAML + Starlark — the authoring guide covers adding routes, fixtures, and state machines. Open an issue for anything big first, or just file a PR — merged routes ship in the next release.