{"openapi":"3.1.0","info":{"title":"Greenset API","version":"1.0.0","contact":{"name":"Greenset","email":"hello@greenset.ai"},"description":"Draw ads from a store, draft them, activate them and watch them, from your own code. Every call acts for the one workspace its key belongs to.\n\nAuthenticate with `Authorization: Bearer gsk_live_…`. Keys are made under Developers in Greenset and shown once. Cookies and keys in the URL are refused; there is no CORS, so call the API from a server.\n\nRate limits by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys (headers RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset; 429 carries Retry-After). Images a day: Free 6, the trial 15, Premium and Agency no daily count (fair use). Images and store reads are shared with the console.\n\nMoney: a draft never spends. Activation needs the `activate` scope, the daily budget typed back as `confirm_budget_usd`, and passes the same hard daily cap (210% worst-day rule: Meta can deliver up to 2.1× a daily budget in one day), ad-balance and live-writes checks as the console. The API can lower limits; raising them needs a person in the console.\n\nErrors are always `{\"error\": {\"code\", \"message\", \"request_id\"}}`. Every answer carries an X-Request-Id header; quote it when you write to Greenset. Lists page with `limit` and `cursor` and answer `has_more` and `next_cursor`.\n\nVersioning: every route is under /v1. No /v1 route has been removed. Routes, fields and error codes that change are listed in the changelog at https://app.greenset.ai/docs#changelog; the wording of a fact or a message can change without a row."},"externalDocs":{"description":"The API reference, readable without signing in","url":"https://app.greenset.ai/docs"},"servers":[{"url":"https://app.greenset.ai"}],"tags":[{"name":"Account"},{"name":"Stores"},{"name":"Studio"},{"name":"Drafts"},{"name":"Campaigns"},{"name":"Safety"},{"name":"Autopilot"},{"name":"Webhooks"},{"name":"Reference"}],"paths":{"/v1/openapi.json":{"get":{"operationId":"getOpenApi","summary":"This API's OpenAPI 3.1 document","description":"Public and cacheable. The Developers screen renders its reference from this document; generate a client from it if you like.","tags":["Reference"],"security":[],"x-greenset-scope":null,"parameters":[],"responses":{"200":{"description":"The OpenAPI document (the example is abridged).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"},"example":{"openapi":"3.1.0","info":{"title":"Greenset API","version":"1.0.0"},"paths":{}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/openapi.json"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/openapi.json\", {\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/openapi.json\",\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/account":{"get":{"operationId":"getAccount","summary":"Setup lamps, balance, caps and this key","description":"The six setup checks Setup shows, with times in UTC. Also the ad balance read from Whop (cached up to a minute), the hard daily cap and what it allows, whether live writes are on, today's allowances, and which scopes this key holds.","tags":["Account"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[],"responses":{"200":{"description":"The workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Account"},"example":{"workspace":{"name":"Northfield Coffee"},"ad_account":{"id":"biz_LiveChild001","name":"Northfield Coffee","linked_at":"2026-10-01T09:14:02.000Z"},"setup":[{"id":"account","status":"pass","fact":"owner@northfieldcoffee.example · confirmed 2026-10-01 09:12 UTC"},{"id":"adaccount","status":"pass","fact":"biz_••d001 · pays from your ad balance · since 2026-10-01 09:14 UTC"},{"id":"facebook","status":"pass","fact":"\"Northfield Coffee\" · sacc••Qp1Z · can advertise"},{"id":"pixel","status":"pass","fact":"Whop confirmed the pixel on northfieldcoffee.example · 2026-10-02 10:30 UTC"},{"id":"balance","status":"pass","fact":"$412.50 available"},{"id":"draft","status":"next","fact":"Draw ads for what you sell in Studio"}],"balance":{"available_usd":412.5,"pending_usd":0,"credits_usd":0},"payment_failed":null,"caps":{"hard_daily_cap_usd":500,"max_single_daily_usd":238.09,"min_daily_budget_usd":5,"overdelivery":2.1},"live_writes":{"enabled":true},"quotas":{"scrape":{"used":0,"limit":120},"photos":{"used":0,"limit":40},"copilot":{"used":0,"limit":200},"elements":{"used":0,"limit":120}},"key":{"id":"key_Ab12Cd34Ef56","name":"Zapier","scopes":["read","stores","studio","drafts","activate","stop"],"expires_at":"2026-11-04T13:29:30.000Z"},"as_of":"2026-10-05T13:31:07.000Z"}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/account \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/account\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/account\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/stores":{"get":{"operationId":"listStores","summary":"Connected stores","description":"Every store connected to the workspace, with its catalog, conversions and pixel state. Credentials never appear.","tags":["Stores"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[],"responses":{"200":{"description":"Stores (one page, at most 100: a workspace has few).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StorePage"},"example":{"data":[{"id":"conn_9Xk2Lm4Pq7Rs","platform":"shopify","store_url":"https://northfieldcoffee.example","name":"Northfield Coffee","status":"connected","status_detail":null,"connected_at":"2026-10-01T09:20:44.000Z","catalog":{"count":38,"total":38,"synced_at":"2026-10-01T09:20:47.000Z","source":"shopify-json"},"conversions":{"mode":"pixel","state":"dry_run","last_event_at":null},"pixel":{"installed":true,"verified_at":"2026-10-02T10:30:05.000Z","method":null}}],"has_more":false,"next_cursor":null}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/stores \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/stores\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/stores\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]},"post":{"operationId":"addStore","summary":"Add a store by URL","description":"Detects the platform, connects the store by its public pages and imports up to `limit` products. Uses one store read from today's allowance. A new store counts toward the plan's stores (Free and Premium 1, Agency its store count); past it, 402 `upgrade_required`. Reconnecting a store already here doesn't count. Connecting with admin credentials (Shopify, WordPress) stays in the console, because it needs the store owner's consent screen. A store Greenset has seen on a Whop ad account that Whop suspended or restricted is refused (`store_gated`), as in the console; when Greenset can't confirm that with Whop, nothing is linked (`upstream_error`, safe to retry).","tags":["Stores"],"security":[{"apiKey":[]}],"x-greenset-scope":"stores","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The store and what was imported.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StoreAdded"},"example":{"store":{"id":"conn_9Xk2Lm4Pq7Rs","platform":"shopify","store_url":"https://northfieldcoffee.example","name":"Northfield Coffee","status":"connected","status_detail":null,"connected_at":"2026-10-01T09:20:44.000Z","catalog":{"count":38,"total":38,"synced_at":"2026-10-01T09:20:47.000Z","source":"shopify-json"},"conversions":{"mode":"pixel","state":"dry_run","last_event_at":null},"pixel":{"installed":true,"verified_at":"2026-10-02T10:30:05.000Z","method":null}},"catalog":{"count":38,"total":38,"source":"shopify-json","notes":[]}}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON. `destination_refused`: The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"destination_refused":{"summary":"destination_refused","value":{"error":{"code":"destination_refused","message":"The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"402":{"description":"`upgrade_required`: The plan's allowance is used (Free: 6 images a day; the trial: 15), or the feature is part of Premium. The message says which; Plan in Greenset upgrades.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upgrade_required":{"summary":"upgrade_required","value":{"error":{"code":"upgrade_required","message":"The plan's allowance is used (Free: 6 images a day; the trial: 15), or the feature is part of Premium. The message says which; Plan in Greenset upgrades.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running. `store_gated`: Refused for one of two reasons: the store's platform can't be connected by URL, or Greenset has seen the store on a Whop ad account that Whop suspended or restricted. The message says which.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"store_gated":{"summary":"store_gated","value":{"error":{"code":"store_gated","message":"Refused for one of two reasons: the store's platform can't be connected by URL, or Greenset has seen the store on a Whop ad account that Whop suspended or restricted. The message says which.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds. `quota_exceeded`: Today's allowance for this kind of work is used. It resets at midnight UTC.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"quota_exceeded":{"summary":"quota_exceeded","value":{"error":{"code":"quota_exceeded","message":"Today's allowance for this kind of work is used. It resets at midnight UTC.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/stores \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"url\": \"https://northfieldcoffee.example\"\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/stores\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"url\": \"https://northfieldcoffee.example\"\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"url\": \"https://northfieldcoffee.example\",\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/stores\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":4,"maxLength":500,"description":"The store's address."},"limit":{"type":"integer","minimum":1,"maximum":1000,"description":"Products to import (default 250)."}}},"example":{"url":"https://northfieldcoffee.example"}}}}}},"/v1/concepts":{"post":{"operationId":"drawConcepts","summary":"Draw up to six ads from a store","description":"Reads the store, cuts out its products and renders up to six ad concepts (`count`, default 6) in Whop's three formats (square, vertical, horizontal). Takes 20–60 s. Each concept is one image from the plan's daily allowance (Free 6, the trial 15, Premium and Agency fair use); fewer are made when fewer are left, and any that fail to render are given back (`render: false` uses a store read instead and returns copy without images). Console and API renders share the same two render slots.","tags":["Studio"],"security":[{"apiKey":[]}],"x-greenset-scope":"studio","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The concepts drawn (up to six), best first, and today's image allowance after this draw.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConceptBatch"},"example":{"batch_id":"bat_4e8a1c2b9d70","brand":{"name":"Northfield Coffee","platform":"shopify","colors":["#2B1D14","#E8D9C5"]},"concepts":[{"id":"cpt_1a2b3c4d5e6f","batch_id":"bat_4e8a1c2b9d70","rank":1,"brand":"Northfield Coffee","source_url":"https://northfieldcoffee.example","hook":"desire","headline":"Roasted Tuesday, at your door Friday","subline":"Small-batch beans, shipped weekly","cta":"Shop now","call_to_action":"shop_now","score":8,"layout":"hero","copy":{"headlines":["Roasted Tuesday, at your door Friday"],"primary_texts":["Small-batch coffee from our roastery, shipped the week it's roasted."],"descriptions":["House blend, $18"]},"product":{"title":"House blend","price":"$18.00","url":"https://northfieldcoffee.example/products/house-blend"},"images":{"square":"/v1/concepts/cpt_1a2b3c4d5e6f/images/square","vertical":"/v1/concepts/cpt_1a2b3c4d5e6f/images/vertical","horizontal":"/v1/concepts/cpt_1a2b3c4d5e6f/images/horizontal"}}],"warnings":[],"images":{"tier":"free","used":6,"limit":6,"left":0,"unlimited":false}}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON. `destination_refused`: The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"destination_refused":{"summary":"destination_refused","value":{"error":{"code":"destination_refused","message":"The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"402":{"description":"`upgrade_required`: The plan's allowance is used (Free: 6 images a day; the trial: 15), or the feature is part of Premium. The message says which; Plan in Greenset upgrades.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upgrade_required":{"summary":"upgrade_required","value":{"error":{"code":"upgrade_required","message":"The plan's allowance is used (Free: 6 images a day; the trial: 15), or the feature is part of Premium. The message says which; Plan in Greenset upgrades.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours. `unprocessable`: The input is well-formed but can't be used (the message says why).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"unprocessable":{"summary":"unprocessable","value":{"error":{"code":"unprocessable","message":"The input is well-formed but can't be used (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds. `quota_exceeded`: Today's allowance for this kind of work is used. It resets at midnight UTC.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"quota_exceeded":{"summary":"quota_exceeded","value":{"error":{"code":"quota_exceeded","message":"Today's allowance for this kind of work is used. It resets at midnight UTC.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"503":{"description":"`busy`: The studio is busy rendering. Retry in a minute.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"busy":{"summary":"busy","value":{"error":{"code":"busy","message":"The studio is busy rendering. Retry in a minute.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/concepts \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"url\": \"https://northfieldcoffee.example\"\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/concepts\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"url\": \"https://northfieldcoffee.example\"\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"url\": \"https://northfieldcoffee.example\",\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/concepts\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","minLength":4,"maxLength":500},"render":{"type":"boolean","description":"Default true."},"count":{"type":"integer","minimum":1,"maximum":6,"description":"How many concepts to render (default 6); each is one image from the daily allowance."}}},"example":{"url":"https://northfieldcoffee.example"}}}}}},"/v1/concepts/{id}":{"get":{"operationId":"getConcept","summary":"One concept","description":"Copy, product and image links for one concept drawn in this workspace.","tags":["Studio"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Concept id (cpt_ and 12 hex characters).","schema":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$","example":"cpt_1a2b3c4d5e6f"},"example":"cpt_1a2b3c4d5e6f"}],"responses":{"200":{"description":"The concept.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Concept"},"example":{"id":"cpt_1a2b3c4d5e6f","batch_id":"bat_4e8a1c2b9d70","rank":1,"brand":"Northfield Coffee","source_url":"https://northfieldcoffee.example","hook":"desire","headline":"Roasted Tuesday, at your door Friday","subline":"Small-batch beans, shipped weekly","cta":"Shop now","call_to_action":"shop_now","score":8,"layout":"hero","copy":{"headlines":["Roasted Tuesday, at your door Friday"],"primary_texts":["Small-batch coffee from our roastery, shipped the week it's roasted."],"descriptions":["House blend, $18"]},"product":{"title":"House blend","price":"$18.00","url":"https://northfieldcoffee.example/products/house-blend"},"images":{"square":"/v1/concepts/cpt_1a2b3c4d5e6f/images/square","vertical":"/v1/concepts/cpt_1a2b3c4d5e6f/images/vertical","horizontal":"/v1/concepts/cpt_1a2b3c4d5e6f/images/horizontal"}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/concepts/{id}/images/{format}":{"get":{"operationId":"getConceptImage","summary":"A concept's rendered image","description":"The PNG Greenset rendered for one format. These are the exact bytes a live draft uploads to Whop.","tags":["Studio"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Concept id (cpt_ and 12 hex characters).","schema":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$","example":"cpt_1a2b3c4d5e6f"},"example":"cpt_1a2b3c4d5e6f"},{"name":"format","in":"path","required":true,"description":"square (1:1), vertical (9:16) or horizontal (1.91:1).","schema":{"type":"string","enum":["square","vertical","horizontal"],"example":"square"},"example":"square"}],"responses":{"200":{"description":"The PNG: square 1080×1080, vertical 1080×1920, horizontal 1200×628 pixels.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"image/png":{"schema":{"type":"string","contentMediaType":"image/png"}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f/images/square \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -o greenset-square.png"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { writeFile } from \"node:fs/promises\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f/images/square\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nif (!res.ok) {\n  const { error } = await res.json();\n  throw new Error(`${res.status} ${error.code}: ${error.message}`);\n}\nawait writeFile(\"greenset-square.png\", Buffer.from(await res.arrayBuffer()));"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/concepts/cpt_1a2b3c4d5e6f/images/square\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res, open(\"greenset-square.png\", \"wb\") as f:\n        f.write(res.read())\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")"}]}},"/v1/drafts":{"get":{"operationId":"listDrafts","summary":"Drafts, newest first","description":"Dry runs and live drafts. A live draft exists in Whop as a draft campaign and cannot spend until it is activated.","tags":["Drafts"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, 1–100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","maxLength":600}}],"responses":{"200":{"description":"A page of drafts.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftPage"},"example":{"data":[{"id":"drf_0a1b2c3d4e5f","concept_id":"cpt_1a2b3c4d5e6f","mode":"live","status":"created","title":"House blend · autumn","daily_budget_usd":25,"countries":["US"],"destination_url":"https://northfieldcoffee.example/products/house-blend","campaign_id":"adcamp_8Hq2LmN4pR7sTv","error":null,"created_at":"2026-10-05T13:40:00.000Z","updated_at":"2026-10-05T13:40:09.000Z","placements":{"mode":"automatic","preset":"automatic","facebook":null,"instagram":null,"other":[],"count":null,"of":19,"label":"Automatic"}}],"has_more":false,"next_cursor":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_cursor`: The cursor isn't one this API returned.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_cursor":{"summary":"invalid_cursor","value":{"error":{"code":"invalid_cursor","message":"The cursor isn't one this API returned.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/drafts \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/drafts\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/drafts\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]},"post":{"operationId":"createDraft","summary":"Draft an ad from a concept","description":"Checks the budget against Greenset's minimum and your hard daily cap, cleans Whop's reserved parameters off the link, and plans the Whop requests. A dry run (the default) creates nothing in Whop and sends none of the planned requests. Where your workspace has an ad account, it reads that account from Whop and, unless the ad links to a whop.com page, asks Whop whether the pixel is on that page. `live: true` with `confirm_budget_usd` equal to `daily_budget_usd` creates the campaign in Whop as a draft (0 spend) when live writes are on; when they're off, you get a dry run and `gated_reason` says why.","tags":["Drafts"],"security":[{"apiKey":[]}],"x-greenset-scope":"drafts","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The draft, its checks and the requests Greenset plans to send to Whop.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftPlan"},"example":{"id":"drf_6f5e4d3c2b1a","mode":"dry_run","gated_reason":null,"planned_requests":[{"step":"create_campaign","method":"POST","path":"/ad_campaigns","body":{"title":"House blend · autumn"}}],"checks":[{"id":"whop_limits","status":"pass","detail":"Within Whop's account limits."}],"removed_params":[],"result":null,"placements":{"mode":"automatic","preset":"automatic","facebook":null,"instagram":null,"other":[],"count":null,"of":19,"label":"Automatic"},"placements_check":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON. `confirm_required`: A typed confirmation is missing. `confirm_mismatch`: The typed daily budget doesn't equal the draft's daily budget. `destination_refused`: The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"confirm_required":{"summary":"confirm_required","value":{"error":{"code":"confirm_required","message":"A typed confirmation is missing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"confirm_mismatch":{"summary":"confirm_mismatch","value":{"error":{"code":"confirm_mismatch","message":"The typed daily budget doesn't equal the draft's daily budget.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"destination_refused":{"summary":"destination_refused","value":{"error":{"code":"destination_refused","message":"The URL points somewhere Greenset won't fetch (private network, wrong scheme or port).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running. `conflict`: The request can't be applied in the current state (the message says why). `no_ad_account`: The workspace has no Whop ad account yet. Finish Setup. `account_not_ready`: The ad account isn't ready for writes (Setup shows what's left).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"conflict":{"summary":"conflict","value":{"error":{"code":"conflict","message":"The request can't be applied in the current state (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"no_ad_account":{"summary":"no_ad_account","value":{"error":{"code":"no_ad_account","message":"The workspace has no Whop ad account yet. Finish Setup.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"account_not_ready":{"summary":"account_not_ready","value":{"error":{"code":"account_not_ready","message":"The ad account isn't ready for writes (Setup shows what's left).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours. `unprocessable`: The input is well-formed but can't be used (the message says why).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"unprocessable":{"summary":"unprocessable","value":{"error":{"code":"unprocessable","message":"The input is well-formed but can't be used (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/drafts \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"concept_id\": \"cpt_1a2b3c4d5e6f\",\n    \"destination_url\": \"https://northfieldcoffee.example/products/house-blend\",\n    \"daily_budget_usd\": 25\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/drafts\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"concept_id\": \"cpt_1a2b3c4d5e6f\",\n    \"destination_url\": \"https://northfieldcoffee.example/products/house-blend\",\n    \"daily_budget_usd\": 25\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"concept_id\": \"cpt_1a2b3c4d5e6f\",\n    \"destination_url\": \"https://northfieldcoffee.example/products/house-blend\",\n    \"daily_budget_usd\": 25,\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/drafts\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["concept_id","destination_url","daily_budget_usd"],"properties":{"concept_id":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$"},"destination_url":{"type":"string","minLength":8,"maxLength":2000,"description":"Where the ad sends people (https)."},"daily_budget_usd":{"type":"number","minimum":5,"maximum":100000,"description":"US dollars a day: the ad's daily budget. Meta can spend up to 210% of it in one day, and that worst day has to fit under your hard daily cap."},"countries":{"type":"array","minItems":1,"maxItems":50,"items":{"type":"string","pattern":"^[A-Za-z]{2}$"},"description":"ISO 3166-1 alpha-2 (default US)."},"live":{"type":"boolean","description":"Create it in Whop (as a draft that can't spend). Default false."},"confirm_budget_usd":{"type":"number","description":"Required with live: true. Must equal daily_budget_usd."},"placements":{"type":"object","additionalProperties":false,"required":["mode"],"description":"Where the ad shows. Leave it out for Automatic (Meta chooses, across Facebook, Instagram, Messenger, Audience Network, Threads and WhatsApp). `manual` takes Facebook and Instagram places only; at least one place, and a platform sent with a list needs at least one place in it. Meta's rules on which places go together are checked before anything reaches Whop: On Facebook, Marketplace, Search results, Profile feed and Notifications can show only alongside Facebook Feed (Meta's rule). On Facebook, Stories can show only alongside Facebook Feed or Instagram Stories (Meta's rule). On Facebook, In-stream videos can show only alongside Facebook Feed (Meta's rule).","properties":{"mode":{"type":"string","enum":["automatic","manual"]},"facebook":{"type":["string","array"],"items":{"type":"string","enum":["feed","right_hand_column","marketplace","search","profile_feed","notification","story","instream_video","facebook_reels","facebook_reels_overlay","biz_disco_feed"]},"description":"\"all\", or a list of Facebook places: feed (Feed), right_hand_column (Right column), marketplace (Marketplace), search (Search results), profile_feed (Profile feed), notification (Notifications), story (Stories), instream_video (In-stream videos), facebook_reels (Reels), facebook_reels_overlay (Ads on Reels), biz_disco_feed (Business Explore)."},"instagram":{"type":["string","array"],"items":{"type":"string","enum":["stream","story","explore","explore_home","reels","profile_feed","profile_reels","ig_search"]},"description":"\"all\", or a list of Instagram places: stream (Feed), story (Stories), explore (Explore), explore_home (Explore home), reels (Reels), profile_feed (Profile feed), profile_reels (Profile reels), ig_search (Search results)."}}}}},"example":{"concept_id":"cpt_1a2b3c4d5e6f","destination_url":"https://northfieldcoffee.example/products/house-blend","daily_budget_usd":25}}}}}},"/v1/drafts/{id}":{"get":{"operationId":"getDraft","summary":"One draft, with its planned Whop requests","description":"Includes `planned_requests`: every request Greenset sends (or would send) to Whop for this draft, with idempotency keys.","tags":["Drafts"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"id","in":"path","required":true,"description":"Draft id (drf_ and 12 hex characters).","schema":{"type":"string","pattern":"^drf_[0-9a-f]{12}$","example":"drf_0a1b2c3d4e5f"},"example":"drf_0a1b2c3d4e5f"}],"responses":{"200":{"description":"The draft.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftDetail"},"example":{"id":"drf_0a1b2c3d4e5f","concept_id":"cpt_1a2b3c4d5e6f","mode":"live","status":"created","title":"House blend · autumn","daily_budget_usd":25,"countries":["US"],"destination_url":"https://northfieldcoffee.example/products/house-blend","campaign_id":"adcamp_8Hq2LmN4pR7sTv","error":null,"created_at":"2026-10-05T13:40:00.000Z","updated_at":"2026-10-05T13:40:09.000Z","placements":{"mode":"automatic","preset":"automatic","facebook":null,"instagram":null,"other":[],"count":null,"of":19,"label":"Automatic"},"planned_requests":[{"step":"create_campaign","method":"POST","path":"/ad_campaigns","body":{"title":"House blend · autumn"}}]}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/drafts/{id}/activate":{"post":{"operationId":"activateDraft","summary":"Start a live draft spending","description":"Moves money. Type the draft's daily budget back as `confirm_budget_usd`. Greenset then reads today's spend and every live budget from Whop, refuses if this budget's worst day (210% of it, Meta's ceiling with budget sharing) would take you over your hard daily cap, refuses if your ad balance can't cover one day (and no card is saved), refuses while a failed payment is unresolved or live writes are off, and only then asks Whop to start the campaign. Any read that fails refuses. Activations in a workspace run one at a time, behind any Live ads change or Autopilot cycle that can raise spend: one waits at most 20 seconds, then is refused with nothing started, and once its turn comes the workspace's ad account is checked again.","tags":["Drafts"],"security":[{"apiKey":[]}],"x-greenset-scope":"activate","x-greenset-moves-money":true,"parameters":[{"name":"id","in":"path","required":true,"description":"Draft id (drf_ and 12 hex characters).","schema":{"type":"string","pattern":"^drf_[0-9a-f]{12}$","example":"drf_0a1b2c3d4e5f"},"example":"drf_0a1b2c3d4e5f"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"Activated: Whop has the campaign and it is in Meta's review. `stopped_again` says when Stop everything or the hard daily cap's pause ran while it was being started (Greenset paused it again right after).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftActivation"},"example":{"activated":true,"campaign_id":"adcamp_8Hq2LmN4pR7sTv","verdict":"needs_approval","cap":{"day":"2026-10-05","time_zone":"America/New_York","spent_today_usd":0,"committed_usd":0,"after_usd":25,"worst_case_usd":52.5,"cap_usd":500},"stopped_again":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON. `confirm_required`: A typed confirmation is missing. `confirm_mismatch`: The typed daily budget doesn't equal the draft's daily budget.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"confirm_required":{"summary":"confirm_required","value":{"error":{"code":"confirm_required","message":"A typed confirmation is missing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"confirm_mismatch":{"summary":"confirm_mismatch","value":{"error":{"code":"confirm_mismatch","message":"The typed daily budget doesn't equal the draft's daily budget.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running. `activation_refused`: A spend check refused activation (hard daily cap, ad balance, live writes, Stop everything since it began) or Whop refused the start. Nothing was started. `activation_unknown`: Whop's answer to the start was lost and couldn't be proven either way: the campaign may have started. Greenset counts it against the hard daily cap; check the campaign in Launch and activate again only if Whop still shows a draft. `no_ad_account`: The workspace has no Whop ad account yet. Finish Setup. `account_not_ready`: The ad account isn't ready for writes (Setup shows what's left). `conflict`: The request can't be applied in the current state (the message says why).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"activation_refused":{"summary":"activation_refused","value":{"error":{"code":"activation_refused","message":"A spend check refused activation (hard daily cap, ad balance, live writes, Stop everything since it began) or Whop refused the start. Nothing was started.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"activation_unknown":{"summary":"activation_unknown","value":{"error":{"code":"activation_unknown","message":"Whop's answer to the start was lost and couldn't be proven either way: the campaign may have started. Greenset counts it against the hard daily cap; check the campaign in Launch and activate again only if Whop still shows a draft.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"no_ad_account":{"summary":"no_ad_account","value":{"error":{"code":"no_ad_account","message":"The workspace has no Whop ad account yet. Finish Setup.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"account_not_ready":{"summary":"account_not_ready","value":{"error":{"code":"account_not_ready","message":"The ad account isn't ready for writes (Setup shows what's left).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"conflict":{"summary":"conflict","value":{"error":{"code":"conflict","message":"The request can't be applied in the current state (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f/activate \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"confirm_budget_usd\": 25\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f/activate\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"confirm_budget_usd\": 25\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"confirm_budget_usd\": 25,\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/drafts/drf_0a1b2c3d4e5f/activate\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["confirm_budget_usd"],"properties":{"confirm_budget_usd":{"type":"number","exclusiveMinimum":0,"description":"The draft's daily budget, typed back."},"acknowledge_review":{"type":"boolean","description":"Send `true` once you've read the words the policy screen flagged, when its check says `review` (an ambiguous term such as \"supplement\"). Without it such a draft is refused with `activation_refused`. The acknowledgement is recorded on the draft against the key's owner."}}},"example":{"confirm_budget_usd":25}}}}}},"/v1/stop-all":{"post":{"operationId":"stopAll","summary":"Pause every live campaign","description":"Send `{\"confirm\": \"STOP\"}`. Pauses every live campaign of this workspace's own Whop ad account through Whop's pause action and logs each one. Works whatever state Setup is in. While live writes are off, it pauses nothing and returns what it would pause; it reads your campaigns and account settings from Whop. Whop's Ads Terms: a paused campaign may still run for up to 24 hours after Whop receives the pause.","tags":["Safety"],"security":[{"apiKey":[]}],"x-greenset-scope":"stop","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"What was paused (or would be).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopAllResult"},"example":{"dry_run":false,"gated_reason":null,"account":"biz_••d001","live_found":1,"paused_count":1,"failed_count":0,"results":[{"campaign_id":"adcamp_8Hq2LmN4pR7sTv","title":"House blend · autumn","status":"active","paused":true,"would_pause":false,"whop_status":200,"error":null}],"note":"Whop's Ads Terms say a paused campaign may still run for up to 24 hours after Whop receives the pause, and charges before the pause still apply."}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON. `confirm_required`: A typed confirmation is missing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"confirm_required":{"summary":"confirm_required","value":{"error":{"code":"confirm_required","message":"A typed confirmation is missing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running. `no_ad_account`: The workspace has no Whop ad account yet. Finish Setup. `conflict`: The request can't be applied in the current state (the message says why).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"no_ad_account":{"summary":"no_ad_account","value":{"error":{"code":"no_ad_account","message":"The workspace has no Whop ad account yet. Finish Setup.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"conflict":{"summary":"conflict","value":{"error":{"code":"conflict","message":"The request can't be applied in the current state (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/stop-all \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"confirm\": \"STOP\"\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/stop-all\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"confirm\": \"STOP\"\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"confirm\": \"STOP\",\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/stop-all\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["confirm"],"properties":{"confirm":{"type":"string","enum":["STOP"]}}},"example":{"confirm":"STOP"}}}}}},"/v1/campaigns":{"get":{"operationId":"listCampaigns","summary":"Campaigns with Whop's numbers","description":"Read live from Whop for the window you ask for, on your ad account's own clock, last-touch. A number Whop doesn't report stays `null` (never 0). ROAS counts only payments Whop processes. Every page carries `as_of`.","tags":["Campaigns"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"stats_from","in":"query","required":false,"description":"YYYY-MM-DD on the account's clock (default today).","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"stats_to","in":"query","required":false,"description":"YYYY-MM-DD (default stats_from).","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1–100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","maxLength":600}}],"responses":{"200":{"description":"A page of campaigns.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignPage"},"example":{"data":[{"id":"adcamp_8Hq2LmN4pR7sTv","title":"House blend · autumn","status":"active","delivery_status":"active","platform":"meta","objective":"sales","budget_amount":25,"budget_type":"daily","spend":18.2,"spend_currency":"usd","impressions":2140,"clicks":61,"results":null,"result_event":null,"cost_per_result":null,"purchases":null,"purchase_value":null,"return_on_ad_spend":0,"created_at":"2026-10-05T13:40:00.000Z","updated_at":"2026-10-05T14:00:00.000Z"}],"has_more":false,"next_cursor":null,"window":{"from":"2026-10-05","to":"2026-10-05","time_zone":"America/New_York","time_zone_note":"Day boundaries use America/New_York, your Whop account's reporting time zone.","attribution_model":"last_touch"},"roas_label":"Whop-checkout ROAS, last-touch","as_of":"2026-10-05T14:02:11.000Z"}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_cursor`: The cursor isn't one this API returned.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_cursor":{"summary":"invalid_cursor","value":{"error":{"code":"invalid_cursor","message":"The cursor isn't one this API returned.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`no_ad_account`: The workspace has no Whop ad account yet. Finish Setup.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"no_ad_account":{"summary":"no_ad_account","value":{"error":{"code":"no_ad_account","message":"The workspace has no Whop ad account yet. Finish Setup.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"502":{"description":"`upstream_error`: Whop or the store didn't answer as expected. Safe to retry.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"upstream_error":{"summary":"upstream_error","value":{"error":{"code":"upstream_error","message":"Whop or the store didn't answer as expected. Safe to retry.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/campaigns \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/campaigns\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/campaigns\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/events":{"get":{"operationId":"listEvents","summary":"What Whop told Greenset, newest first","description":"Deposits, payment failures, campaigns delivering, ads rejected, balance and pixel alerts, as recorded for this workspace.","tags":["Campaigns"],"security":[{"apiKey":[]}],"x-greenset-scope":"read","parameters":[{"name":"limit","in":"query","required":false,"description":"Rows per page, 1–100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","maxLength":600}}],"responses":{"200":{"description":"A page of events. `type` is Whop's event name as Greenset recorded it: a Whop deposit.succeeded is listed here as deposit.succeeded and sent to webhook endpoints as deposit.received (src/platform/events.ts).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventPage"},"example":{"data":[{"id":"pev_Qm3Xr8Tz1Kp5","type":"deposit.succeeded","resource_id":null,"summary":"Deposit $500.00 reached the ad balance","created_at":"2026-10-05T13:58:02.000Z"}],"has_more":false,"next_cursor":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_cursor`: The cursor isn't one this API returned.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_cursor":{"summary":"invalid_cursor","value":{"error":{"code":"invalid_cursor","message":"The cursor isn't one this API returned.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/events \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/events\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/events\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/guardrails":{"get":{"operationId":"getGuardrails","summary":"Spend cap, ROAS floor, CPA target and autonomy","description":"The guardrails every spend decision is checked against: the hard daily cap, Autopilot's ROAS floor and CPA target, and the autonomy level per kind of action.","tags":["Autopilot"],"security":[{"apiKey":[]}],"x-greenset-scope":"autopilot","parameters":[],"responses":{"200":{"description":"Guardrails.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guardrails"},"example":{"autonomy":{"read":"Auto","pause":"Confirm","budget":"Suggest","creative":"Suggest","launch":"Confirm","billing":"Notify","destination":"Notify"},"hard_daily_cap_usd":500,"roas_floor":3,"cpa_target_usd":null}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/guardrails \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/guardrails\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/guardrails\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]},"put":{"operationId":"tightenGuardrails","summary":"Tighten guardrails","description":"Send only the fields you change. The API can only make guardrails stricter: lower the hard daily cap, raise the ROAS floor, lower an existing CPA target, or move an action to less autonomy (Auto → Confirm → Suggest → Notify). Anything that loosens is refused with `loosen_requires_console` and nothing is saved; a person makes that change in the console.","tags":["Autopilot"],"security":[{"apiKey":[]}],"x-greenset-scope":"autopilot","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"The saved guardrails, every field.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guardrails"},"example":{"autonomy":{"read":"Auto","pause":"Confirm","budget":"Notify","creative":"Suggest","launch":"Confirm","billing":"Notify","destination":"Notify"},"hard_daily_cap_usd":250,"roas_floor":3,"cpa_target_usd":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`). `loosen_requires_console`: The API can tighten guardrails, never loosen them. Raise a limit in the console.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"loosen_requires_console":{"summary":"loosen_requires_console","value":{"error":{"code":"loosen_requires_console","message":"The API can tighten guardrails, never loosen them. Raise a limit in the console.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X PUT https://app.greenset.ai/v1/guardrails \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"hard_daily_cap_usd\": 250,\n    \"autonomy\": {\n      \"budget\": \"Notify\"\n    }\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/guardrails\", {\n  method: \"PUT\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"hard_daily_cap_usd\": 250,\n    \"autonomy\": {\n      \"budget\": \"Notify\"\n    }\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"hard_daily_cap_usd\": 250,\n    \"autonomy\": {\n        \"budget\": \"Notify\",\n    },\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/guardrails\",\n    method=\"PUT\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"hard_daily_cap_usd":{"type":"number","exclusiveMinimum":0,"maximum":100000,"description":"US dollars a day, on the ad account's clock. A budget is refused when the live daily budgets with it could spend more than this in one day at Meta's 210% ceiling, and Greenset pauses every live campaign after today's spend reaches it."},"roas_floor":{"type":"number","minimum":0,"maximum":100,"description":"A multiple of ad spend, not a percent: 3 means 3×. For ad groups measured on purchases: one whose return on ad spend from Whop checkouts is under it gets no budget raise from Autopilot or a rule."},"cpa_target_usd":{"type":"number","exclusiveMinimum":0,"maximum":100000,"description":"US dollars: what one result should cost. Autopilot counts an ad group's spend in multiples of it when it decides whether to pause one that has no results."},"autonomy":{"type":"object","additionalProperties":false,"description":"Per kind of action: Notify | Suggest | Confirm | Auto (billing and destination can never be Auto).","properties":{"read":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"pause":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"budget":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"creative":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"launch":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"billing":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"destination":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]}}}}},"example":{"hard_daily_cap_usd":250,"autonomy":{"budget":"Notify"}}}}}}},"/v1/webhooks":{"get":{"operationId":"listWebhooks","summary":"Webhook endpoints","description":"Every endpoint of the workspace. Secrets are never returned after creation.","tags":["Webhooks"],"security":[{"apiKey":[]}],"x-greenset-scope":"webhooks","parameters":[],"responses":{"200":{"description":"Endpoints (one page: a workspace has at most 10).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointPage"},"example":{"data":[{"id":"we_Rk7Lq2Xm9Pz4Tn6V","url":"https://hooks.example.com/greenset","description":"Slack relay","events":["campaign.live","payment.failed","balance.low"],"enabled":true,"disabled_reason":null,"disabled_at":null,"consecutive_failures":0,"created_at":"2026-10-03T08:00:00.000Z","last_success_at":"2026-10-05T13:41:02.000Z","last_failure_at":null}],"has_more":false,"next_cursor":null}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/webhooks \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/webhooks\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/webhooks\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]},"post":{"operationId":"createWebhook","summary":"Add a webhook endpoint","description":"An https URL on a public host (port 443, no IP addresses, nothing that resolves to a private network) and the events it wants. The answer carries the endpoint's `whsec_` signing secret once; store it. Up to 10 endpoints per workspace. The workspace's owners get an email whenever an endpoint is added.","tags":["Webhooks"],"security":[{"apiKey":[]}],"x-greenset-scope":"webhooks","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"201":{"description":"The endpoint and its secret (shown once).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"},"example":{"endpoint":{"id":"we_Rk7Lq2Xm9Pz4Tn6V","url":"https://hooks.example.com/greenset","description":"Slack relay","events":["campaign.live","payment.failed","balance.low"],"enabled":true,"disabled_reason":null,"disabled_at":null,"consecutive_failures":0,"created_at":"2026-10-03T08:00:00.000Z","last_success_at":null,"last_failure_at":null},"secret":"whsec_(shown once)"}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_json`: The body isn't valid JSON.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_json":{"summary":"invalid_json","value":{"error":{"code":"invalid_json","message":"The body isn't valid JSON.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running. `conflict`: The request can't be applied in the current state (the message says why).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"conflict":{"summary":"conflict","value":{"error":{"code":"conflict","message":"The request can't be applied in the current state (the message says why).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"413":{"description":"`payload_too_large`: Bodies are limited to 64 KB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"payload_too_large":{"summary":"payload_too_large","value":{"error":{"code":"payload_too_large","message":"Bodies are limited to 64 KB.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/webhooks \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\n    \"url\": \"https://hooks.example.com/greenset\",\n    \"events\": [\n      \"campaign.live\",\n      \"payment.failed\",\n      \"balance.low\"\n    ]\n  }'"},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/webhooks\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Content-Type\": \"application/json\",\n    \"Idempotency-Key\": randomUUID(),\n  },\n  body: JSON.stringify({\n    \"url\": \"https://hooks.example.com/greenset\",\n    \"events\": [\n      \"campaign.live\",\n      \"payment.failed\",\n      \"balance.low\"\n    ]\n  }),\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nbody = {\n    \"url\": \"https://hooks.example.com/greenset\",\n    \"events\": [\"campaign.live\", \"payment.failed\", \"balance.low\"],\n}\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/webhooks\",\n    method=\"POST\",\n    data=json.dumps(body).encode(),\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Content-Type\": \"application/json\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["url","events"],"properties":{"url":{"type":"string","minLength":10,"maxLength":500},"events":{"type":"array","minItems":1,"maxItems":12,"items":{"type":"string","enum":["deposit.received","campaign.submitted","campaign.live","ad.rejected","payment.failed","balance.low","cap.reached","autopilot.proposal","automation.fired","competitor.changed","pixel.stopped","account.restricted"]}},"description":{"type":"string","maxLength":120}}},"example":{"url":"https://hooks.example.com/greenset","events":["campaign.live","payment.failed","balance.low"]}}}}}},"/v1/webhooks/{id}":{"delete":{"operationId":"deleteWebhook","summary":"Delete a webhook endpoint","description":"Stops all deliveries to it at once. Its delivery log goes with it.","tags":["Webhooks"],"security":[{"apiKey":[]}],"x-greenset-scope":"webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"Endpoint id (we_ and 16 characters).","schema":{"type":"string","pattern":"^we_[A-Za-z0-9_-]{6,40}$","description":"Endpoint id","example":"we_Rk7Lq2Xm9Pz4Tn6V"},"example":"we_Rk7Lq2Xm9Pz4Tn6V"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"Deleted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeleted"},"example":{"deleted":true,"id":"we_Rk7Lq2Xm9Pz4Tn6V"}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X DELETE https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Idempotency-Key\": randomUUID(),\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V\",\n    method=\"DELETE\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/webhooks/{id}/test":{"post":{"operationId":"testWebhook","summary":"Send a test event now","description":"One signed `webhook.test` delivery to this endpoint, right now, whether it is on or off. Not retried and not counted toward switch-off. Up to 10 a minute per workspace.","tags":["Webhooks"],"security":[{"apiKey":[]}],"x-greenset-scope":"webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"Endpoint id (we_ and 16 characters).","schema":{"type":"string","pattern":"^we_[A-Za-z0-9_-]{6,40}$","description":"Endpoint id","example":"we_Rk7Lq2Xm9Pz4Tn6V"},"example":"we_Rk7Lq2Xm9Pz4Tn6V"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Up to 255 printable characters. The first answer is stored for 24 hours; a retry with the same key and the same request gets that answer back (header Idempotent-Replayed: true) instead of running twice.","schema":{"type":"string","maxLength":255}}],"responses":{"200":{"description":"What the endpoint answered.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestResult"},"example":{"delivery_id":"dlv_Hn4Wq8Zr2Lm6Pk1S","event_id":"msg_Vb5Nc9Xe3Rt7Yu2I","ok":true,"status":204,"error":null,"ms":182}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"409":{"description":"`idempotency_in_progress`: A request with this Idempotency-Key is still running.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_in_progress":{"summary":"idempotency_in_progress","value":{"error":{"code":"idempotency_in_progress","message":"A request with this Idempotency-Key is still running.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"422":{"description":"`idempotency_key_reused`: This Idempotency-Key was used for a different request in the last 24 hours.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"idempotency_key_reused":{"summary":"idempotency_key_reused","value":{"error":{"code":"idempotency_key_reused","message":"This Idempotency-Key was used for a different request in the last 24 hours.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"428":{"description":"`terms_update_required`: Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"terms_update_required":{"summary":"terms_update_required","value":{"error":{"code":"terms_update_required","message":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl -X POST https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/test \\\n  -H \"Authorization: Bearer $GREENSET_KEY\" \\\n  -H \"Idempotency-Key: $(uuidgen)\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\nimport { randomUUID } from \"node:crypto\";\n\nconst res = await fetch(\"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/test\", {\n  method: \"POST\",\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n    \"Idempotency-Key\": randomUUID(),\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request, uuid\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/test\",\n    method=\"POST\",\n    data=b\"\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}},"/v1/webhooks/{id}/deliveries":{"get":{"operationId":"listWebhookDeliveries","summary":"An endpoint's delivery log","description":"Each delivery with its status, attempts and every attempt's HTTP status code.","tags":["Webhooks"],"security":[{"apiKey":[]}],"x-greenset-scope":"webhooks","parameters":[{"name":"id","in":"path","required":true,"description":"Endpoint id (we_ and 16 characters).","schema":{"type":"string","pattern":"^we_[A-Za-z0-9_-]{6,40}$","description":"Endpoint id","example":"we_Rk7Lq2Xm9Pz4Tn6V"},"example":"we_Rk7Lq2Xm9Pz4Tn6V"},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1–100 (default 20).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","maxLength":600}}],"responses":{"200":{"description":"A page of deliveries, newest first.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryPage"},"example":{"data":[{"id":"dlv_Hn4Wq8Zr2Lm6Pk1S","event_id":"msg_Vb5Nc9Xe3Rt7Yu2I","type":"campaign.live","status":"succeeded","attempts":1,"last_status":200,"last_error":null,"next_attempt_at":null,"created_at":"2026-10-05T13:41:01.000Z","tries":[{"attempt":1,"status":200,"error":null,"ms":140,"at":"2026-10-05T13:41:02.000Z"}]}],"has_more":false,"next_cursor":null}}}},"400":{"description":"`invalid_request`: A field is missing, has the wrong type, or is out of range (`field` names it). `invalid_cursor`: The cursor isn't one this API returned.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"invalid_request":{"summary":"invalid_request","value":{"error":{"code":"invalid_request","message":"A field is missing, has the wrong type, or is out of range (`field` names it).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"invalid_cursor":{"summary":"invalid_cursor","value":{"error":{"code":"invalid_cursor","message":"The cursor isn't one this API returned.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"401":{"description":"`unauthorized`: No key, a malformed key, or a key Greenset doesn't know. `key_revoked`: The key was revoked. Create a new one under Developers. `key_expired`: The key passed its expiry date. `key_owner_inactive`: The person who made the key is no longer an active member of the workspace. `key_in_url`: A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"unauthorized":{"summary":"unauthorized","value":{"error":{"code":"unauthorized","message":"No key, a malformed key, or a key Greenset doesn't know.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_revoked":{"summary":"key_revoked","value":{"error":{"code":"key_revoked","message":"The key was revoked. Create a new one under Developers.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_expired":{"summary":"key_expired","value":{"error":{"code":"key_expired","message":"The key passed its expiry date.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_owner_inactive":{"summary":"key_owner_inactive","value":{"error":{"code":"key_owner_inactive","message":"The person who made the key is no longer an active member of the workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"key_in_url":{"summary":"key_in_url","value":{"error":{"code":"key_in_url","message":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"403":{"description":"`forbidden_origin`: Browser requests from other sites are refused. Call the API from your server. `insufficient_scope`: The key doesn't hold the scope this route needs (`required_scope`).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"forbidden_origin":{"summary":"forbidden_origin","value":{"error":{"code":"forbidden_origin","message":"Browser requests from other sites are refused. Call the API from your server.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}},"insufficient_scope":{"summary":"insufficient_scope","value":{"error":{"code":"insufficient_scope","message":"The key doesn't hold the scope this route needs (`required_scope`).","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"404":{"description":"`not_found`: Nothing with that id in this workspace.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"not_found":{"summary":"not_found","value":{"error":{"code":"not_found","message":"Nothing with that id in this workspace.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"429":{"description":"`rate_limited`: More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"rate_limited":{"summary":"rate_limited","value":{"error":{"code":"rate_limited","message":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}},"500":{"description":"`internal_error`: Greenset failed. Nothing was half-done that it didn't say.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"examples":{"internal_error":{"summary":"internal_error","value":{"error":{"code":"internal_error","message":"Greenset failed. Nothing was half-done that it didn't say.","request_id":"req_7Hk2Lm9Pq4Rs8Tv1"}}}}}}}},"x-codeSamples":[{"lang":"Shell","label":"curl","source":"curl https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/deliveries \\\n  -H \"Authorization: Bearer $GREENSET_KEY\""},{"lang":"JavaScript","label":"JavaScript","source":"// Node 18 or later. Save as greenset.mjs, then: node greenset.mjs\n\nconst res = await fetch(\"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/deliveries\", {\n  headers: {\n    Authorization: `Bearer ${process.env.GREENSET_KEY}`,\n  },\n});\nconst data = await res.json();\nif (!res.ok) throw new Error(`${res.status} ${data.error.code}: ${data.error.message}`);\nconsole.log(data);"},{"lang":"Python","label":"Python","source":"# Python 3, standard library only.\nimport json, os, urllib.request\nfrom urllib.error import HTTPError\n\nreq = urllib.request.Request(\n    \"https://app.greenset.ai/v1/webhooks/we_Rk7Lq2Xm9Pz4Tn6V/deliveries\",\n    headers={\n        \"Authorization\": \"Bearer \" + os.environ[\"GREENSET_KEY\"],\n    },\n)\ntry:\n    with urllib.request.urlopen(req) as res:\n        data = json.load(res)\nexcept HTTPError as e:\n    err = json.load(e)[\"error\"]\n    raise SystemExit(f\"{e.code} {err['code']}: {err['message']}\")\nprint(data)"}]}}},"webhooks":{"deposit.received":{"post":{"operationId":"webhook_deposit_received","summary":"Funds reached your ad balance.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `deposit.received`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["deposit.received"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["deposit_id","amount","currency"],"properties":{"deposit_id":{"type":["string","null"],"description":"Whop's deposit id."},"amount":{"type":["number","null"],"description":"Major units: 48.5 is $48.50. null when Whop's payload isn't its documented shape."},"currency":{"type":"string","description":"Lower-case currency code, such as usd."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"campaign.submitted":{"post":{"operationId":"webhook_campaign_submitted","summary":"A draft was activated and sent to Meta for review.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `campaign.submitted`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["campaign.submitted"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["campaign_id","title","daily_budget_usd","draft_id"],"properties":{"campaign_id":{"type":"string"},"title":{"type":"string"},"daily_budget_usd":{"type":"number"},"draft_id":{"type":["string","null"]}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"campaign.live":{"post":{"operationId":"webhook_campaign_live","summary":"Whop reports a campaign delivering.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `campaign.live`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["campaign.live"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["campaign_id","title","delivery_status"],"properties":{"campaign_id":{"type":"string"},"title":{"type":"string"},"delivery_status":{"type":"string","description":"Whop's delivery status."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"ad.rejected":{"post":{"operationId":"webhook_ad_rejected","summary":"Meta rejected an ad; Whop's first reason is included.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `ad.rejected`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["ad.rejected"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["ad_id","title","reason"],"properties":{"ad_id":{"type":["string","null"]},"title":{"type":"string"},"reason":{"type":"string","description":"Whop's first reason."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"payment.failed":{"post":{"operationId":"webhook_payment_failed","summary":"Whop couldn't collect ad spend; delivery is paused until it's fixed.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `payment.failed`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["payment.failed"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["campaign_id","reason"],"properties":{"campaign_id":{"type":["string","null"]},"reason":{"type":"string"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"balance.low":{"post":{"operationId":"webhook_balance_low","summary":"About three days or less of ads left at today's budgets.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `balance.low`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["balance.low"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["balance_usd","daily_budget_usd","days_left"],"properties":{"balance_usd":{"type":["number","null"]},"daily_budget_usd":{"type":["number","null"]},"days_left":{"type":["number","null"]}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"cap.reached":{"post":{"operationId":"webhook_cap_reached","summary":"Today's spend reached your hard daily cap; Greenset paused live campaigns.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `cap.reached`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["cap.reached"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["cap_usd","spent_usd","paused_campaigns","day"],"properties":{"cap_usd":{"type":"number"},"spent_usd":{"type":["number","null"]},"paused_campaigns":{"type":"number"},"day":{"type":"string","description":"YYYY-MM-DD on the ad account's clock."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"autopilot.proposal":{"post":{"operationId":"webhook_autopilot_proposal","summary":"Autopilot has a change waiting for your approval.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `autopilot.proposal`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["autopilot.proposal"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["proposal_id","summary"],"properties":{"proposal_id":{"type":"string"},"summary":{"type":"string"}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"automation.fired":{"post":{"operationId":"webhook_automation_fired","summary":"A rule you set fired.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `automation.fired`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["automation.fired"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["rule_id","rule_name","action","entities"],"properties":{"rule_id":{"type":"string"},"rule_name":{"type":"string"},"action":{"type":"string"},"entities":{"type":"array","maxItems":20,"items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"outcome":{"type":"string"}}},"description":"What the rule acted on, at most 20."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"competitor.changed":{"post":{"operationId":"webhook_competitor_changed","summary":"A competitor you track changed an offer, a price or its ads.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `competitor.changed`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["competitor.changed"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["competitor_id","name","changes"],"properties":{"competitor_id":{"type":"string"},"name":{"type":"string"},"changes":{"type":"array","maxItems":20,"items":{"type":"object","properties":{"kind":{"type":"string"},"summary":{"type":"string"}}},"description":"What changed, at most 20."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"pixel.stopped":{"post":{"operationId":"webhook_pixel_stopped","summary":"Whop stopped seeing its pixel on one of your stores.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `pixel.stopped`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["pixel.stopped"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["connection_id","host","days_since_seen"],"properties":{"connection_id":{"type":["string","null"],"description":"The connected store, or null when the page is a live ad's destination that isn't a connected store."},"host":{"type":"string"},"days_since_seen":{"type":["number","null"]}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"account.restricted":{"post":{"operationId":"webhook_account_restricted","summary":"Whop reports your ad account can't launch ads.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `account.restricted`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["account.restricted"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["reason","run_ads"],"properties":{"reason":{"type":"string"},"run_ads":{"type":["string","null"],"description":"Whop's run_ads capability state."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}},"webhook.test":{"post":{"operationId":"webhook_webhook_test","summary":"Sent only by “Send test event”.","tags":["Webhooks"],"description":"Greenset POSTs this to every enabled endpoint subscribed to `webhook.test`. Headers: webhook-id (same on every retry), webhook-timestamp (Unix seconds), webhook-signature (\"v1,\" + base64 HMAC-SHA256 over \"{id}.{timestamp}.{body}\" with the base64-decoded part of your whsec_ secret). Answer any 2xx within 10 s. Up to 8 attempts: the first right away, then retries after 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h, each wait counted from the attempt before, so the last is about 20.6 hours after the first; nothing is sent once 24 hours have passed. 20 failed attempts in a row switch the endpoint off, and the workspace's owners get one email.","parameters":[{"name":"webhook-id","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-timestamp","in":"header","required":true,"schema":{"type":"string"}},{"name":"webhook-signature","in":"header","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","timestamp","data"],"properties":{"type":{"type":"string","enum":["webhook.test"]},"timestamp":{"type":"string","format":"date-time"},"data":{"type":"object","required":["message","workspace"],"properties":{"message":{"type":"string"},"workspace":{"type":"string","description":"The workspace's name."}}}}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried."}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"gsk_live_<43 characters>","description":"A Greenset API key."}},"headers":{"X-Request-Id":{"description":"This request's id (req_ and 16 characters), on every /v1 answer. Error bodies repeat it as error.request_id.","schema":{"type":"string","pattern":"^req_[A-Za-z0-9_-]{16}$"}}},"schemas":{"OpenApiDocument":{"type":"object","required":["openapi","info","paths"],"properties":{"openapi":{"type":"string"},"info":{"type":"object"},"paths":{"type":"object"}},"description":"This document."},"Account":{"type":"object","required":["workspace","ad_account","setup","balance","payment_failed","caps","live_writes","quotas","key","as_of"],"properties":{"workspace":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}},"ad_account":{"type":["object","null"],"required":["id","name","linked_at"],"properties":{"id":{"type":"string","description":"The Whop account (biz_…)."},"name":{"type":"string"},"linked_at":{"type":["string","null"]}}},"setup":{"type":"array","items":{"type":"object","required":["id","status","fact"],"properties":{"id":{"type":"string","description":"account, adaccount, facebook, pixel, balance or draft."},"status":{"type":"string","enum":["pass","next","todo","fail"]},"fact":{"type":"string"}}},"description":"The six setup checks Setup shows, with times in UTC. The first check's fact names the sign-in email of the person who made this key."},"balance":{"type":["object","null"],"required":["available_usd","pending_usd","credits_usd"],"properties":{"available_usd":{"type":["number","null"]},"pending_usd":{"type":["number","null"]},"credits_usd":{"type":["number","null"]}}},"payment_failed":{"type":["object","null"],"required":["since","issue"],"properties":{"since":{"type":"string"},"issue":{"type":["string","null"]}}},"caps":{"type":"object","required":["hard_daily_cap_usd","max_single_daily_usd","min_daily_budget_usd","overdelivery"],"properties":{"hard_daily_cap_usd":{"type":"number"},"max_single_daily_usd":{"type":"number"},"min_daily_budget_usd":{"type":"number"},"overdelivery":{"type":"number"}}},"live_writes":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"reason":{"type":"string"}}},"quotas":{"type":"object","description":"Today's allowances by kind (UTC day): `scrape` is store reads, `photos` is Studio photo sets, `copilot` is questions to Greeni, `elements` is money screens opened in Setup. Growth runs and Autopilot cycles count against daily limits of their own (100 each unless set otherwise), not listed here. Images drawn are not here: they are counted by plan, and `POST /v1/concepts` answers today's count as `images`.","additionalProperties":{"type":"object","required":["used","limit"],"properties":{"used":{"type":"integer"},"limit":{"type":"number"}}}},"key":{"type":"object","required":["id","name","scopes","expires_at"],"properties":{"id":{"type":"string","pattern":"^key_[A-Za-z0-9_-]{12}$","description":"This key's id."},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["read","stores","studio","drafts","activate","stop","autopilot","webhooks"]}},"expires_at":{"type":["string","null"]}}},"as_of":{"type":"string","format":"date-time"}}},"Store":{"type":"object","required":["id","platform","store_url","name","status","status_detail","connected_at","catalog","conversions","pixel"],"properties":{"id":{"type":"string","pattern":"^conn_[A-Za-z0-9_-]{12}$","description":"Store connection id."},"platform":{"type":"string"},"store_url":{"type":"string"},"name":{"type":"string"},"status":{"type":"string"},"status_detail":{"type":["string","null"]},"connected_at":{"type":"string"},"catalog":{"type":"object","required":["count","total","synced_at","source"],"properties":{"count":{"type":"integer"},"total":{"type":["integer","null"]},"synced_at":{"type":["string","null"]},"source":{"type":["string","null"]}}},"conversions":{"type":"object","required":["mode","state","last_event_at"],"properties":{"mode":{"type":"string","enum":["server","pixel","none"]},"state":{"type":"string"},"last_event_at":{"type":["string","null"]}}},"pixel":{"type":"object","required":["installed","verified_at","method"],"properties":{"installed":{"type":["boolean","null"]},"verified_at":{"type":["string","null"]},"method":{"type":["string","null"]}}}}},"StorePage":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Store"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"StoreAdded":{"type":"object","required":["store","catalog"],"properties":{"store":{"$ref":"#/components/schemas/Store"},"catalog":{"type":"object","required":["count","total","source","notes"],"properties":{"count":{"type":"integer"},"total":{"type":["integer","null"]},"source":{"type":"string"},"notes":{"type":"array","items":{"type":"string"}}}}}},"Concept":{"type":"object","required":["id","batch_id","rank","brand","source_url","hook","headline","subline","cta","call_to_action","score","layout","copy","product","images"],"properties":{"id":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$","description":"Concept id."},"batch_id":{"type":"string","description":"The draw it came from (bat_…)."},"rank":{"type":"integer"},"brand":{"type":["string","null"]},"source_url":{"type":["string","null"]},"hook":{"type":["string","null"]},"headline":{"type":["string","null"]},"subline":{"type":["string","null"]},"cta":{"type":["string","null"]},"call_to_action":{"type":["string","null"]},"score":{"type":["number","null"]},"layout":{"type":["string","null"]},"copy":{"type":"object","required":["headlines","primary_texts","descriptions"],"properties":{"headlines":{"type":"array","items":{"type":"string"}},"primary_texts":{"type":"array","items":{"type":"string"}},"descriptions":{"type":"array","items":{"type":"string"}}}},"product":{"type":["object","null"],"required":["title","price","url"],"properties":{"title":{"type":["string","null"]},"price":{"type":["string","null"]},"url":{"type":["string","null"]}}},"images":{"type":"object","required":[],"properties":{"square":{"type":"string"},"vertical":{"type":"string"},"horizontal":{"type":"string"}},"description":"The /v1 path of each format rendered (a concept drawn without render has none)."}}},"ConceptBatch":{"type":"object","required":["batch_id","brand","concepts","warnings","images"],"properties":{"batch_id":{"type":"string","description":"This draw (bat_…)."},"brand":{"type":"object","required":["name","platform","colors"],"properties":{"name":{"type":"string"},"platform":{"type":["string","null"]},"colors":{"type":"array","items":{"type":"string"}}}},"concepts":{"type":"array","items":{"$ref":"#/components/schemas/Concept"}},"warnings":{"type":"array","items":{"type":"string"}},"images":{"type":"object","required":["tier","used","limit","left","unlimited"],"properties":{"tier":{"type":"string","enum":["free","trial","premium","agency"]},"used":{"type":"integer"},"limit":{"type":"integer"},"left":{"type":"integer"},"unlimited":{"type":"boolean","description":"Premium and Agency: no daily count; `limit` is then the fair-use ceiling."}},"description":"Today's image allowance (UTC day)."}}},"Draft":{"type":"object","required":["id","concept_id","mode","status","title","daily_budget_usd","countries","destination_url","campaign_id","error","created_at","updated_at","placements"],"properties":{"id":{"type":"string","pattern":"^drf_[0-9a-f]{12}$","description":"Draft id."},"concept_id":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$","description":"The concept it was drafted from."},"mode":{"type":"string","enum":["dry_run","live"]},"status":{"type":"string"},"title":{"type":["string","null"]},"daily_budget_usd":{"type":"number"},"countries":{"type":"array","items":{"type":"string"}},"destination_url":{"type":"string"},"campaign_id":{"type":["string","null"]},"error":{"type":["string","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"placements":{"type":"object","properties":{"mode":{"type":"string","enum":["automatic","manual"]},"preset":{"type":"string","enum":["automatic","facebook_instagram","facebook","instagram","other"]},"facebook":{"type":["string","array","null"],"items":{"type":"string"}},"instagram":{"type":["string","array","null"],"items":{"type":"string"}},"other":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"positions":{"type":"array","items":{"type":"string"}}}}},"count":{"type":["integer","null"]},"of":{"type":"integer"},"label":{"type":"string"},"legacy":{"type":"boolean"}}}}},"DraftPage":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Draft"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"DraftDetail":{"type":"object","required":["id","concept_id","mode","status","title","daily_budget_usd","countries","destination_url","campaign_id","error","created_at","updated_at","placements","planned_requests"],"properties":{"id":{"type":"string","pattern":"^drf_[0-9a-f]{12}$","description":"Draft id."},"concept_id":{"type":"string","pattern":"^cpt_[0-9a-f]{12}$","description":"The concept it was drafted from."},"mode":{"type":"string","enum":["dry_run","live"]},"status":{"type":"string"},"title":{"type":["string","null"]},"daily_budget_usd":{"type":"number"},"countries":{"type":"array","items":{"type":"string"}},"destination_url":{"type":"string"},"campaign_id":{"type":["string","null"]},"error":{"type":["string","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]},"placements":{"type":"object","properties":{"mode":{"type":"string","enum":["automatic","manual"]},"preset":{"type":"string","enum":["automatic","facebook_instagram","facebook","instagram","other"]},"facebook":{"type":["string","array","null"],"items":{"type":"string"}},"instagram":{"type":["string","array","null"],"items":{"type":"string"}},"other":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"positions":{"type":"array","items":{"type":"string"}}}}},"count":{"type":["integer","null"]},"of":{"type":"integer"},"label":{"type":"string"},"legacy":{"type":"boolean"}}},"planned_requests":{"type":"array","items":{"type":"object","required":["step","method","path"],"properties":{"step":{"type":"string"},"method":{"type":"string","enum":["GET","POST","PUT","PATCH"]},"path":{"type":"string","description":"Whop API path."},"headers":{"type":"object","additionalProperties":{"type":"string"}},"body":{"description":"The JSON body Greenset sends."},"note":{"type":"string"}},"description":"One request Greenset sends (or, on a dry run, would send) to Whop."}}}},"DraftPlan":{"type":"object","required":["id","mode","gated_reason","planned_requests","checks","removed_params","result","placements","placements_check"],"properties":{"id":{"type":"string","pattern":"^drf_[0-9a-f]{12}$","description":"Draft id."},"mode":{"type":"string","enum":["dry_run","live"]},"gated_reason":{"type":["string","null"]},"planned_requests":{"type":"array","items":{"type":"object","required":["step","method","path"],"properties":{"step":{"type":"string"},"method":{"type":"string","enum":["GET","POST","PUT","PATCH"]},"path":{"type":"string","description":"Whop API path."},"headers":{"type":"object","additionalProperties":{"type":"string"}},"body":{"description":"The JSON body Greenset sends."},"note":{"type":"string"}},"description":"One request Greenset sends (or, on a dry run, would send) to Whop."}},"checks":{"type":"array","items":{"type":"object","required":["id","status","detail"],"properties":{"id":{"type":"string","enum":["whop_requirements","ads_agreement","payment_method","facebook_page","pixel","ad_balance","creative","policy","whop_limits"]},"status":{"type":"string","enum":["pass","fail","unknown","review"]},"detail":{"type":"string"}},"description":"One launch check (Launch's ladder)."}},"removed_params":{"type":"array","items":{"type":"object","required":["param","value","reason"],"properties":{"param":{"type":"string"},"value":{"type":"string"},"reason":{"type":"string"}}}},"result":{"type":["object","null"],"additionalProperties":{"type":"string"},"description":"Live creates: the ids Whop gave back (ad_campaign_id, ad_group_id, …)."},"placements":{"type":"object","properties":{"mode":{"type":"string","enum":["automatic","manual"]},"preset":{"type":"string","enum":["automatic","facebook_instagram","facebook","instagram","other"]},"facebook":{"type":["string","array","null"],"items":{"type":"string"}},"instagram":{"type":["string","array","null"],"items":{"type":"string"}},"other":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"positions":{"type":"array","items":{"type":"string"}}}}},"count":{"type":["integer","null"]},"of":{"type":"integer"},"label":{"type":"string"},"legacy":{"type":"boolean"}}},"placements_check":{"type":["object","null"],"required":["ok","in_whop","sentence"],"properties":{"ok":{"type":"boolean"},"in_whop":{"type":["object","null"],"properties":{"mode":{"type":"string","enum":["automatic","manual"]},"preset":{"type":"string","enum":["automatic","facebook_instagram","facebook","instagram","other"]},"facebook":{"type":["string","array","null"],"items":{"type":"string"}},"instagram":{"type":["string","array","null"],"items":{"type":"string"}},"other":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"positions":{"type":"array","items":{"type":"string"}}}}},"count":{"type":["integer","null"]},"of":{"type":"integer"},"label":{"type":"string"},"legacy":{"type":"boolean"}}},"sentence":{"type":"string"}},"description":"Live creates only: where Whop holds the ad as showing."}}},"DraftActivation":{"type":"object","required":["activated","campaign_id","verdict","cap","stopped_again"],"properties":{"activated":{"type":"boolean","enum":[true]},"campaign_id":{"type":"string"},"verdict":{"type":"string","description":"What the guardrails said of this start: `needs_approval` (the typed budget was the approval) or, with Launch autonomy on Auto, `allow_and_notify`."},"cap":{"type":["object","null"],"required":["day","time_zone","spent_today_usd","committed_usd","after_usd","worst_case_usd","cap_usd"],"properties":{"day":{"type":"string","description":"YYYY-MM-DD on the ad account's clock."},"time_zone":{"type":"string"},"spent_today_usd":{"type":"number"},"committed_usd":{"type":"number"},"after_usd":{"type":"number"},"worst_case_usd":{"type":"number"},"cap_usd":{"type":"number"}},"description":"The hard daily cap as activation read it."},"stopped_again":{"type":["object","null"],"required":["paused","unknown","reason"],"properties":{"paused":{"type":"boolean","description":"Whop confirmed the pause."},"unknown":{"type":"boolean","description":"Whop's answer to the pause was lost: it may still be spending."},"reason":{"type":"string","description":"What happened, in words: tell the person this."}},"description":"Stop everything or the hard daily cap's pause ran while this campaign was being started, so Greenset paused it again right after. Null when nothing did."}}},"StopAllResult":{"type":"object","required":["dry_run","gated_reason","account","live_found","paused_count","failed_count","results","note"],"properties":{"dry_run":{"type":"boolean"},"gated_reason":{"type":["string","null"]},"account":{"type":"string","description":"The ad account, shortened."},"live_found":{"type":"integer"},"paused_count":{"type":"integer"},"failed_count":{"type":"integer"},"results":{"type":"array","items":{"type":"object","required":["campaign_id","title","status","paused","would_pause","whop_status","error"],"properties":{"campaign_id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"paused":{"type":"boolean"},"would_pause":{"type":"boolean"},"whop_status":{"type":["integer","null"]},"error":{"type":["string","null"]}}}},"note":{"type":"string"}}},"Campaign":{"type":"object","required":["id","title","status","delivery_status","platform","objective","budget_amount","budget_type","spend","spend_currency","impressions","clicks","results","result_event","cost_per_result","purchases","purchase_value","return_on_ad_spend","created_at","updated_at"],"properties":{"id":{"type":["string","null"]},"title":{"type":["string","null"]},"status":{"type":["string","null"]},"delivery_status":{"type":["string","null"]},"platform":{"type":["string","null"]},"objective":{"type":["string","null"]},"budget_amount":{"type":["number","null"]},"budget_type":{"type":["string","null"]},"spend":{"type":["number","null"]},"spend_currency":{"type":["string","null"]},"impressions":{"type":["number","null"]},"clicks":{"type":["number","null"]},"results":{"type":["number","null"]},"result_event":{"type":["string","null"]},"cost_per_result":{"type":["number","null"]},"purchases":{"type":["number","null"]},"purchase_value":{"type":["number","null"]},"return_on_ad_spend":{"type":["number","null"]},"created_at":{"type":["string","null"]},"updated_at":{"type":["string","null"]}},"description":"Whop's numbers for the window asked for. A number Whop doesn't report is null, never 0."},"CampaignPage":{"type":"object","required":["data","has_more","next_cursor","window","roas_label","as_of"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]},"window":{"type":"object","required":["from","to","time_zone","time_zone_note","attribution_model"],"properties":{"from":{"type":"string"},"to":{"type":"string"},"time_zone":{"type":"string"},"time_zone_note":{"type":["string","null"]},"attribution_model":{"type":"string"}}},"roas_label":{"type":"string"},"as_of":{"type":"string","format":"date-time"}}},"Event":{"type":"object","required":["id","type","resource_id","summary","created_at"],"properties":{"id":{"type":"string","pattern":"^pev_[A-Za-z0-9_-]{12}$","description":"Event id."},"type":{"type":"string","description":"Whop's event name as recorded (deposit.succeeded, …)."},"resource_id":{"type":["string","null"]},"summary":{"type":"string"},"created_at":{"type":"string"}}},"EventPage":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"Guardrails":{"type":"object","required":["autonomy","hard_daily_cap_usd","roas_floor","cpa_target_usd"],"properties":{"autonomy":{"type":"object","required":["read","pause","budget","creative","launch","billing","destination"],"properties":{"read":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"pause":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"budget":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"creative":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"launch":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"billing":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]},"destination":{"type":"string","enum":["Notify","Suggest","Confirm","Auto"]}}},"hard_daily_cap_usd":{"type":"number"},"roas_floor":{"type":"number"},"cpa_target_usd":{"type":["number","null"]}}},"WebhookEndpoint":{"type":"object","required":["id","url","description","events","enabled","disabled_reason","consecutive_failures","disabled_at","created_at","last_success_at","last_failure_at"],"properties":{"id":{"type":"string","pattern":"^we_[A-Za-z0-9_-]{16}$","description":"Endpoint id."},"url":{"type":"string"},"description":{"type":["string","null"]},"events":{"type":"array","items":{"type":"string","enum":["deposit.received","campaign.submitted","campaign.live","ad.rejected","payment.failed","balance.low","cap.reached","autopilot.proposal","automation.fired","competitor.changed","pixel.stopped","account.restricted"]}},"enabled":{"type":"boolean"},"disabled_reason":{"type":["string","null"]},"consecutive_failures":{"type":"integer"},"disabled_at":{"type":["string","null"]},"created_at":{"type":"string"},"last_success_at":{"type":["string","null"]},"last_failure_at":{"type":["string","null"]}}},"WebhookEndpointPage":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"WebhookEndpointCreated":{"type":"object","required":["endpoint","secret"],"properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint"},"secret":{"type":["string","null"],"description":"The endpoint's whsec_ signing secret, in this answer only. null in an idempotent replay."},"secret_note":{"type":"string","description":"Only in an idempotent replay."}}},"WebhookDeleted":{"type":"object","required":["deleted","id"],"properties":{"deleted":{"type":"boolean","enum":[true]},"id":{"type":"string","pattern":"^we_[A-Za-z0-9_-]{16}$","description":"The endpoint deleted."}}},"WebhookTestResult":{"type":"object","required":["delivery_id","event_id","ok","status","error","ms"],"properties":{"delivery_id":{"type":"string","pattern":"^dlv_[A-Za-z0-9_-]{16}$","description":"Delivery id."},"event_id":{"type":"string","pattern":"^msg_[A-Za-z0-9_-]{16}$","description":"Event id (webhook-id)."},"ok":{"type":"boolean"},"status":{"type":["integer","null"]},"error":{"type":["string","null"]},"ms":{"type":["integer","null"]}}},"Delivery":{"type":"object","required":["id","event_id","type","status","attempts","last_status","last_error","next_attempt_at","created_at","tries"],"properties":{"id":{"type":"string","pattern":"^dlv_[A-Za-z0-9_-]{16}$","description":"Delivery id."},"event_id":{"type":"string","pattern":"^msg_[A-Za-z0-9_-]{16}$","description":"The event's id: the webhook-id header, the same on every retry."},"type":{"type":"string"},"status":{"type":"string","enum":["pending","sending","succeeded","failed","cancelled"]},"attempts":{"type":"integer"},"last_status":{"type":["integer","null"]},"last_error":{"type":["string","null"]},"next_attempt_at":{"type":["string","null"]},"created_at":{"type":"string"},"tries":{"type":"array","items":{"type":"object","required":["attempt","status","error","ms","at"],"properties":{"attempt":{"type":"integer"},"status":{"type":["integer","null"]},"error":{"type":["string","null"]},"ms":{"type":["integer","null"]},"at":{"type":"string"}}},"description":"The latest 10 attempts, newest first."}}},"DeliveryPage":{"type":"object","required":["data","has_more","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Delivery"}},"has_more":{"type":"boolean"},"next_cursor":{"type":["string","null"]}}},"Error":{"type":"object","required":["error"],"description":"Every refusal. Branch on `error.code`; `error.message` is for a person. Some codes add fields at the top level: `field` (which input), `required_scope`, `quota`, `images`, `checks`, `cap`, `activated`, `outcome_unknown`.","properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","enum":["unauthorized","key_revoked","key_expired","key_owner_inactive","key_in_url","forbidden_origin","insufficient_scope","loosen_requires_console","invalid_request","invalid_json","invalid_cursor","confirm_required","confirm_mismatch","destination_refused","not_found","conflict","no_ad_account","account_not_ready","activation_refused","activation_unknown","store_gated","idempotency_in_progress","idempotency_key_reused","unprocessable","payload_too_large","rate_limited","quota_exceeded","terms_update_required","upgrade_required","internal_error","upstream_error","busy"]},"message":{"type":"string"},"request_id":{"type":"string","pattern":"^req_[A-Za-z0-9_-]{16}$","description":"The same value as the X-Request-Id header. Quote it when you write to Greenset."}}},"field":{"type":"string","description":"invalid_request, confirm_required and the like: the input that was refused (body.x, a query parameter, Idempotency-Key)."},"required_scope":{"type":"string","enum":["read","stores","studio","drafts","activate","stop","autopilot","webhooks"],"description":"insufficient_scope: the scope the route needs."},"quota":{"type":"object","description":"quota_exceeded: the daily allowance that ran out.","properties":{"kind":{"type":"string"},"used":{"type":"integer"},"limit":{"type":"number"}}},"images":{"type":"object","description":"upgrade_required and quota_exceeded on POST /v1/concepts: today's image allowance.","properties":{"tier":{"type":"string"},"used":{"type":"integer"},"limit":{"type":"integer"},"left":{"type":"integer"},"unlimited":{"type":"boolean"}}},"checks":{"type":"array","description":"activation_refused and activation_unknown: the checks activation ran.","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string"},"detail":{"type":"string"}}}},"cap":{"type":["object","null"],"description":"activation_refused and activation_unknown: the hard daily cap as activation read it."},"activated":{"type":"boolean","enum":[false],"description":"activation_refused and activation_unknown: always false (activation_unknown: not confirmed, which is not the same as not started)."},"outcome_unknown":{"type":"boolean","enum":[true],"description":"activation_unknown: always true. The campaign may have started; Greenset counts it against the hard daily cap until Whop shows it."}}}}},"x-greenset-scopes":[{"scope":"read","label":"Read","detail":"Account, setup checks, stores, ads, drafts, campaigns and events. Changes nothing.","moves_money":false},{"scope":"stores","label":"Stores","detail":"Add a store by URL and import its catalog.","moves_money":false},{"scope":"studio","label":"Studio","detail":"Draw ads from a store. Each ad uses one of the plan's images a day.","moves_money":false},{"scope":"drafts","label":"Drafts","detail":"Create drafts: a dry run by default; with live: true a draft in Whop, while live writes are on. A draft can't spend.","moves_money":false},{"scope":"activate","label":"Activate","detail":"Start a draft spending. Needs the typed daily budget, and every cap and balance check still applies.","moves_money":true},{"scope":"stop","label":"Stop","detail":"Pause every live campaign (typed STOP).","moves_money":false},{"scope":"autopilot","label":"Autopilot","detail":"Read guardrails and tighten them. Raising a limit needs you in the console.","moves_money":false},{"scope":"webhooks","label":"Webhooks","detail":"Manage webhook endpoints and read deliveries.","moves_money":false}],"x-greenset-errors":[{"code":"unauthorized","status":401,"meaning":"No key, a malformed key, or a key Greenset doesn't know."},{"code":"key_revoked","status":401,"meaning":"The key was revoked. Create a new one under Developers."},{"code":"key_expired","status":401,"meaning":"The key passed its expiry date."},{"code":"key_owner_inactive","status":401,"meaning":"The person who made the key is no longer an active member of the workspace."},{"code":"key_in_url","status":401,"meaning":"A key was sent in the URL. Keys go only in the Authorization header; rotate that key."},{"code":"forbidden_origin","status":403,"meaning":"Browser requests from other sites are refused. Call the API from your server."},{"code":"insufficient_scope","status":403,"meaning":"The key doesn't hold the scope this route needs (`required_scope`)."},{"code":"loosen_requires_console","status":403,"meaning":"The API can tighten guardrails, never loosen them. Raise a limit in the console."},{"code":"invalid_request","status":400,"meaning":"A field is missing, has the wrong type, or is out of range (`field` names it)."},{"code":"invalid_json","status":400,"meaning":"The body isn't valid JSON."},{"code":"invalid_cursor","status":400,"meaning":"The cursor isn't one this API returned."},{"code":"confirm_required","status":400,"meaning":"A typed confirmation is missing."},{"code":"confirm_mismatch","status":400,"meaning":"The typed daily budget doesn't equal the draft's daily budget."},{"code":"destination_refused","status":400,"meaning":"The URL points somewhere Greenset won't fetch (private network, wrong scheme or port)."},{"code":"not_found","status":404,"meaning":"Nothing with that id in this workspace."},{"code":"conflict","status":409,"meaning":"The request can't be applied in the current state (the message says why)."},{"code":"no_ad_account","status":409,"meaning":"The workspace has no Whop ad account yet. Finish Setup."},{"code":"account_not_ready","status":409,"meaning":"The ad account isn't ready for writes (Setup shows what's left)."},{"code":"activation_refused","status":409,"meaning":"A spend check refused activation (hard daily cap, ad balance, live writes, Stop everything since it began) or Whop refused the start. Nothing was started."},{"code":"activation_unknown","status":409,"meaning":"Whop's answer to the start was lost and couldn't be proven either way: the campaign may have started. Greenset counts it against the hard daily cap; check the campaign in Launch and activate again only if Whop still shows a draft."},{"code":"store_gated","status":409,"meaning":"Refused for one of two reasons: the store's platform can't be connected by URL, or Greenset has seen the store on a Whop ad account that Whop suspended or restricted. The message says which."},{"code":"idempotency_in_progress","status":409,"meaning":"A request with this Idempotency-Key is still running."},{"code":"idempotency_key_reused","status":422,"meaning":"This Idempotency-Key was used for a different request in the last 24 hours."},{"code":"unprocessable","status":422,"meaning":"The input is well-formed but can't be used (the message says why)."},{"code":"payload_too_large","status":413,"meaning":"Bodies are limited to 64 KB."},{"code":"rate_limited","status":429,"meaning":"More requests a minute than the plan allows on this key, or across the workspace's keys (by plan, per key: Free 30, the trial 60, Premium 120, Agency 300 requests a minute; twice that across a workspace's keys). Wait Retry-After seconds."},{"code":"quota_exceeded","status":429,"meaning":"Today's allowance for this kind of work is used. It resets at midnight UTC."},{"code":"terms_update_required","status":428,"meaning":"Greenset's Terms changed since the person who made this key agreed. They accept the new version in Greenset; until then the key reads but changes nothing."},{"code":"upgrade_required","status":402,"meaning":"The plan's allowance is used (Free: 6 images a day; the trial: 15), or the feature is part of Premium. The message says which; Plan in Greenset upgrades."},{"code":"internal_error","status":500,"meaning":"Greenset failed. Nothing was half-done that it didn't say."},{"code":"upstream_error","status":502,"meaning":"Whop or the store didn't answer as expected. Safe to retry."},{"code":"busy","status":503,"meaning":"The studio is busy rendering. Retry in a minute."}],"x-greenset-limits":{"keys_per_workspace":20,"money_key_max_days":90,"body_bytes":65536,"idempotency_hours":24,"rate_by_plan":[{"plan":"free","per_key_per_minute":30,"per_workspace_per_minute":60},{"plan":"trial","per_key_per_minute":60,"per_workspace_per_minute":120},{"plan":"premium","per_key_per_minute":120,"per_workspace_per_minute":240},{"plan":"agency","per_key_per_minute":300,"per_workspace_per_minute":600}],"images_by_plan":[{"plan":"free","per_day":6,"fair_use_per_day":null},{"plan":"trial","per_day":15,"fair_use_per_day":null},{"plan":"premium","per_day":null,"fair_use_per_day":300},{"plan":"agency","per_day":null,"fair_use_per_day":1500}],"daily_allowances":[{"kind":"scrape","per_day":120},{"kind":"photos","per_day":40},{"kind":"copilot","per_day":200},{"kind":"elements","per_day":120}]}}