# Oetzi, complete agent reference Oetzi is an AI GTM engineer. It watches X, Reddit, LinkedIn, Threads, Bluesky, Hacker News, and Substack Notes for the conversations that matter to a product, scores every match for buying intent (0-100, banded hot/warm/review), drafts replies in the workspace's voice, tracks which AI engines recommend the product (share of model), and generates short-form content. Everything below is one capability registry, exposed three ways with identical names, inputs, gates, and responses. The human docs live at https://www.oetzi.ai/docs. ## Authentication All programmatic access uses workspace API keys, format `oetzi_<40 hex>`, sent as `Authorization: Bearer oetzi_...`. Keys are created by a human in the app at https://www.oetzi.ai/settings, scope to exactly one workspace, and carry capability scopes: read, write, spend (may use credits), post (may publish). 401 = missing/invalid/revoked key. 403 = no active plan, or scope_denied. ## Three surfaces, one contract - MCP (primary): POST https://www.oetzi.ai/api/mcp, Streamable HTTP, stateless. Each capability is a tool with the same name. Claude Code: `claude mcp add --transport http oetzi https://www.oetzi.ai/api/mcp --header "Authorization: Bearer oetzi_YOUR_KEY"` Generic client: `{"mcpServers":{"oetzi":{"type":"http","url":"https://www.oetzi.ai/api/mcp","headers":{"Authorization":"Bearer oetzi_YOUR_KEY"}}}` - REST: POST https://www.oetzi.ai/api/v1/capabilities/{name} with the input as JSON (GET with query params for read capabilities). Discovery at GET /api/v1/capabilities?q=..., schema at GET /api/v1/capabilities/{name}/schema, spec at https://www.oetzi.ai/api/v1/openapi.json. - CLI: `npx @oetzi/cli run {name} --key=value` (same envelope, exit codes map to error codes). ## The envelope (REST and CLI; MCP returns data as text plus structuredContent) `{ ok, data, error: { code, message, details }, meta: { request_id, capability, dry_run, estimate, credits_spent, credits_remaining, job_id } }` Error codes: approval_required, budget_exceeded, insufficient_credits, scope_denied, not_found, invalid_input, rate_limited, conflict, unsupported, internal. ## Spending rules Capabilities marked "spends credits" accept three extra fields: dry_run, approved, max_credits. Without approved: true AND max_credits they run nothing and return `{ dry_run: true, estimate, balance, next }`. With both, they run when the estimate is within max_credits and the workspace can pay; otherwise budget_exceeded or insufficient_credits. Sweeps that find nothing refund themselves. ## Async work Capabilities marked async return `{ job_id }`. Poll get_job, or call wait_job (blocks up to 25 s per call) until status is done or error. ## Recommended loop 1. get_playbook, returns the live signal list, the loop, and the spending rules. Re-call it each session (it regenerates). 2. get_leads status=new band=hot (then band=warm), poll no more than every 15 minutes. Conversations arrive as they qualify. 3. get_lead for full content before acting, draft_reply for voice-tuned variants, enrich_lead (LinkedIn matches) for a verified contact. 4. mark_lead status=contacted for everything actioned, status=dismissed for off-target ones, this keeps the human inbox in sync. 5. rate_lead feeds the tuning loop. "good" on true matches, "not_relevant" on misses (it dismisses the conversation). ## Capabilities (52) ## Discovery ### capabilities_search Find capabilities by what you want to do (for example "people who liked a post" or "pause a signal"). Returns names, scopes, and whether each spends credits. Follow with capabilities_schema for exact inputs. _scope: read_ Parameters: - query: string 路 Free text. Omit to list everything. - limit: integer 路 1..50 Example input: `{"query":"run a sweep now"}` ### capabilities_schema Exact input schema (JSON Schema) for one capability, plus its scope, whether it spends credits, and whether it returns a job_id. _scope: read_ Parameters: - name (required): string Example input: `{"name":"run_signal_now"}` ## Workspace ### get_workspace This workspace's name, plan, credit balance (credits are the meter for sweeps, checks, renders, and pulls), and the scopes of the key you are using. Check the balance before spending. _scope: read_ No parameters. ### get_playbook The out-of-the-box playbook for this workspace: the current signal list, the recommended loop (refresh, poll, act, mark), and the spending rules. Call this first in a new session, and re-call it to refresh your view of what the workspace is listening for. _scope: read_ No parameters. ## Brain ### list_lessons What this workspace has taught Oetzi by reacting to its work: standing rules from rejected or edited post drafts (the drafter reads these every time), notes on rendered videos, and the 馃憤/馃憥 record on matched conversations grouped by signal and by reason. Read this before drafting anything in the workspace's voice. _scope: read_ No parameters. Example input: `{}` ### forget_lesson Remove one standing post rule or video note so it stops shaping future drafts. Match ratings cannot be forgotten here, re-rate the conversation with rate_lead instead. _scope: write_ Parameters: - kind (required): string 路 one of: post_rule | video_note - id (required): string 路 uuid 路 The lesson id from list_lessons Example input: `{"kind":"post_rule","id":""}` ### get_competitor_report How the market talks about each competitor in the Brain over the last 90 days: matched conversations that name them, how many were complaints, how many were hot, which platforms, three recent examples with lead ids, and the people who were in those threads (feed them to list_people or record_outreach). _scope: read_ Parameters: - competitors: array 路 Override the Brain's competitor list for this call Example input: `{}` ### get_knowledge_graph The workspace as a graph: the brand node, the segments it serves, the content angles it owns, the competitors the market names next to it, and the most recently active people, each linked to the hub they engaged around (a competitor's thread, or the brand). Nodes carry portal hrefs. Use it to pick who to reach and what to say. _scope: read_ Parameters: - people: integer 路 0..80 路 How many people to include (default 40) Example input: `{"people":40}` ### get_voice The workspace's voice: the markdown voice doc (how the author writes: openings, rhythm, vocabulary, always/never rules), the samples of their own writing every draft is few-shot on, what they said about past drafts (sounds like me / not me), and which connected accounts can be pulled for more samples. Read it before writing anything on the workspace's behalf outside draft_reply and draft_posts, which already do. _scope: read_ No parameters. Example input: `{}` ### set_voice Replace the voice doc with the markdown you pass (up to 8,000 chars). Every reply and post drafted after this reads it whole. Pass an empty string to clear it and fall back to the one-line tone from the website scrape. Free. _scope: write_ Parameters: - doc (required): string 路 Markdown. See get_voice for the current one and the section shape generate_voice_doc uses. Example input: `{"doc":"# Voice\n\n## In one line\nDry, technical, allergic to hype.\n\n## Never\n- open with a question"}` ### add_voice_samples Add samples of the author's own writing (posts, emails, DMs they actually wrote, 20 to 2,000 chars each). Newest samples are few-shot in every draft, up to 20 are kept. Duplicates are dropped. Free. _scope: write_ Parameters: - samples (required): array 路 Texts the author wrote themselves Example input: `{"samples":["Spent the weekend ripping out our onboarding flow. Three screens became one. Activation went from 31% to 44%. The lesson was not clever, we just deleted things."]}` ### remove_voice_sample Drop one voice sample by id (ids come from get_voice). Free. _scope: write_ Parameters: - sample_id (required): string 路 uuid Example input: `{"sample_id":""}` ### pull_voice_samples Pull the author's own recent posts from a connected account as voice samples: X (the workspace's connected account, replies and reposts excluded) or Bluesky (public feed). LinkedIn does not expose a member's posts to apps, paste those with add_voice_samples. Free, no vendor cost. _scope: write_ Parameters: - platform (required): string 路 one of: x | bluesky - limit: integer 路 1..30 路 How many recent posts to take (default 15) Example input: `{"platform":"x"}` ### generate_voice_doc Draft the voice doc from evidence: the voice samples, the posts approved in the queue, and the website's one-line tone. Overwrites the current doc, so read get_voice first if a person edited it by hand. Free, one model call. _scope: write_ No parameters. Example input: `{}` ### rate_voice Say whether a drafted reply or post sounded like the author. not_me with a note ("too salesy", "I never say folks") becomes a standing never-rule for every later draft; me examples are shown to the drafter as register. Free. _scope: write_ Parameters: - source (required): string 路 one of: reply | post - ref_id (required): string 路 The conversation id (optionally :variant index) or the social post id - verdict (required): string 路 one of: me | not_me - excerpt (required): string 路 The draft text being judged - note: string 路 Why, in the author's words Example input: `{"source":"reply","ref_id":":0","verdict":"not_me","excerpt":"Great question! ...","note":"I never open with Great question"}` ## Signals ### list_signals List this workspace's listening signals: the natural-language definitions of the conversations being watched, with id, status, and cadence. Use the ids to filter get_leads. _scope: read_ No parameters. ### get_signal One signal in full: definition, status, cadence, the platform queries it compiles to, and its last runs. _scope: read_ Parameters: - signal_id (required): string 路 uuid 路 Signal id from list_signals ### set_signal_status Pause or resume a listening signal. Paused signals stop producing matches until reactivated. Draft signals cannot be activated here, they need compilation in the app first. _scope: write_ Parameters: - signal_id (required): string 路 uuid 路 Signal id from list_signals - status (required): string 路 one of: active | paused ### run_signal_now Sweep one signal now across every platform it watches, instead of waiting for its cadence. Costs 2 credits, refunded automatically when nothing qualifies. Rate limited to one manual sweep per signal every 15 minutes. _scope: spend 路 spends credits (sweep) 路 async, returns job_id_ Parameters: - signal_id (required): string 路 uuid 路 Signal id from list_signals - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"signal_id":"","approved":true,"max_credits":2}` ## Conversations ### get_leads Fetch qualified conversations from this workspace: public posts scored for buying intent, each with the post content (truncated at 1,200 chars, use get_lead for full detail), author, platform URL, intent score (0-100), band (hot/warm/review), a why-it-matched rationale, and a ready-to-send reply draft when available. Filter by signal, status, band, audience (who is talking: in-market buyer, current customer, feature request, competitor user), or time, and sort newest (default), oldest, or by score. Conversations default to status 'new', poll with status=new to drive your loop, then mark_lead what you action. _scope: read_ Parameters: - signal_id: string 路 uuid 路 Limit to one signal (see list_signals) - status: string 路 one of: new | saved | contacted | dismissed 路 Workflow status, 'new' = not yet actioned - band: string 路 one of: hot | warm | review 路 hot = strongest intent - audience: string 路 one of: in_market_buyer | current_customer | feature_request | competitor_user | other 路 WHO is talking: current_customer = churn save / product feedback, feature_request = roadmap input, competitor_user = switch target, in_market_buyer = market to them - since: string 路 date-time 路 ISO 8601 timestamp, only conversations delivered after this - sort: string 路 one of: newest | oldest | score 路 newest (default) | oldest | score (intent, descending) - limit: integer 路 1..50 路 Max results, 1-50 (default 20) Example input: `{"status":"new","band":"hot","limit":10}` ### get_lead Full detail for one conversation: untruncated post content, thread context, author profile fields, reply draft variants, enriched contact when present, and current status/feedback. Call before acting on a conversation you found via get_leads. _scope: read_ Parameters: - lead_id (required): string 路 uuid 路 Conversation id from get_leads ### mark_lead Update a conversation's workflow status after acting on it: 'contacted' when outreach was sent, 'saved' to shortlist, 'dismissed' to skip. Keeps the human inbox in sync with what the agent has done. _scope: write_ Parameters: - lead_id (required): string 路 uuid 路 Conversation id from get_leads - status (required): string 路 one of: saved | contacted | dismissed ### rate_lead Quality feedback on a conversation, the same 馃憤/馃憥 as the human inbox, feeding the signal-tuning loop. feedback='good' confirms the match, feedback='not_relevant' marks a miss and dismisses it. Labels tune the signal's judge overnight, so rate accurately and pass a reason when one fits. _scope: write_ Parameters: - lead_id (required): string 路 uuid 路 Conversation id from get_leads - feedback (required): string 路 one of: good | not_relevant - reason: string 路 one of: wrong_persona | wrong_topic | too_old | competitor_employee 路 Why it was not relevant (only with feedback=not_relevant) ### draft_reply Generate two voice-tuned reply variants for a conversation, each with a why-this-angle note, written for its platform norms. Stored on the conversation, so the human inbox sees the same drafts. 1 credit for the first draft on a conversation, every redraft after that is free. A failed generation refunds itself. _scope: spend 路 spends credits (draft)_ Parameters: - lead_id (required): string 路 uuid 路 Conversation id from get_leads - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. ### translate_post Render a non-English conversation in English: the post body and its thread context, tone and handles preserved. Free. Made once per post and cached, so repeat calls return instantly. get_lead reports lang, call this when it is not 'en'. _scope: read_ Parameters: - lead_id (required): string 路 uuid 路 Conversation id from get_leads Example input: `{"lead_id":""}` ## People ### list_people People who interacted with posts this workspace matched: the authors of conversations and, once engager pulls run, the people who liked, reposted, replied to, or quoted them. Filter by a conversation (lead_id), interaction kind, platform, follower floor, whether a verified contact exists, or a tag. Sorted by most recent interaction. _scope: read_ Parameters: - lead_id: string 路 uuid 路 Only people who touched the post behind this conversation - post_id: string 路 uuid 路 Only people who touched this post (posts.id) - kind: string 路 one of: authored | liked | reposted | replied | commented | quoted - platform: string 路 x | reddit | linkedin | threads | bluesky | hn | substack - min_followers: integer 路 0..9007199254740991 - has_contact: boolean 路 true = has a verified email or phone in this workspace - tag: string - since: string 路 date-time 路 Interactions recorded after this ISO timestamp - limit: integer 路 1..100 Example input: `{"kind":"authored","min_followers":500,"limit":20}` ### get_person One person in full: every platform identity, this workspace's interactions with them (each with the post and the conversation it became), verified contacts, tags, and every outreach recorded against them. Check outreach before contacting anyone. _scope: read_ Parameters: - person_id (required): string 路 uuid 路 Person id from list_people or a conversation's person_id ### tag_person Add or remove free-text tags on a person for this workspace (for example "champion", "do-not-contact", "q4-list"). Tags filter list_people. _scope: write_ Parameters: - person_id (required): string 路 uuid 路 Person id from list_people or a conversation's person_id - add: array - remove: array Example input: `{"person_id":"","add":["champion"]}` ### record_outreach Report that you contacted a person: the channel, what you sent, the outcome, and who you are. This is the handoff that keeps the graph honest, so nobody in the workspace double-contacts. Marks the linked conversation contacted when lead_id is given, and emits outreach.recorded. _scope: write_ Parameters: - person_id (required): string 路 uuid 路 Person id from list_people or a conversation's person_id - channel (required): string 路 x_reply | x_dm | linkedin_dm | linkedin_comment | email | imessage | call | other - message: string 路 What was sent, verbatim or summarized - outcome: string 路 sent | replied | booked | declined | bounced | no_answer - agent: string 路 Who did it: your agent name, or a person - external_ref: string 路 Vendor message id or permalink - lead_id: string 路 uuid 路 The conversation this outreach answers, if any - metadata: object Example input: `{"person_id":"","channel":"x_reply","outcome":"sent","agent":"claude-code","lead_id":""}` ### pull_engagers Pull the people who engaged with a post (liked, reposted, quoted, replied, commented) into the people graph, then read them with list_people. Address the post by conversation (lead_id), post id, or its exact URL. Works on X, Bluesky, LinkedIn, Reddit, and Hacker News. 1 credit per post, refunded when fewer than 3 people come back, free when already pulled. _scope: spend 路 spends credits (engagers) 路 async, returns job_id_ Parameters: - lead_id: string 路 uuid - post_id: string 路 uuid - url: string 路 uri 路 Exact URL of a post Oetzi has already seen - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"lead_id":"","approved":true,"max_credits":1}` ## Share of model ### list_geo_questions The buying questions this workspace tracks across AI engines (ChatGPT, Gemini, Perplexity, Grok, Google AI Mode), as a user would type them. _scope: read_ No parameters. ### get_share_of_model The latest share-of-model board: for every tracked question, which engines mentioned this workspace, at what rank, which competitors they named instead, and the sources they cited. Includes the current content plan (what to publish to close the gaps) when one has been drafted. _scope: read_ No parameters. ### run_geo_check Ask every configured AI engine each tracked question now and record who they recommend. Costs 2 credits per active question. Rate limited to one manual board per hour. _scope: spend 路 spends credits (geo) 路 async, returns job_id_ Parameters: - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"approved":true,"max_credits":10}` ## Content ### list_content Short-form videos and carousels this workspace has generated, with status (rendered = awaiting review, accepted = approved, posted) and download URLs for rendered ones. _scope: read_ Parameters: - status: string 路 one of: draft | rendered | accepted | rejected | superseded | archived | posted - limit: integer 路 1..50 ### generate_content Plan and render new short-form videos or carousels from the workspace brain and asset library. Costs 1 credit per rendered item, failed renders are free. Optional brief steers the whole batch. _scope: spend 路 spends credits (video) 路 async, returns job_id_ Parameters: - count: integer 路 1..5 路 How many to plan, 1-5 (default 1) - brief: string 路 A topic or request the whole batch must be about - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"count":2,"brief":"our pricing page launch","approved":true,"max_credits":2}` ## Publishing ### list_social_accounts Accounts this workspace can publish through: natively connected X, LinkedIn, Bluesky, and a Postiz account with its channels (TikTok, Instagram, YouTube, Threads, and more). Replies need a native account; Postiz publishes originals and video only. _scope: read_ No parameters. ### list_posts The post queue: drafts waiting for review, approved and scheduled posts, what was published (with URLs), failures with their error. Filter by status, platform, kind (original | reply), or a GEO plan item key. _scope: read_ Parameters: - status: string 路 one of: draft | approved | scheduled | posted | failed | rejected - platform: string - kind: string 路 one of: original | reply - plan_item_key: string - limit: integer 路 1..200 Example input: `{"status":"draft","limit":20}` ### get_post One queued or published post in full, with its topic, rationale, reply target, media, and publish result. _scope: read_ Parameters: - post_id (required): string 路 uuid 路 Post id from list_posts ### create_post Put a post you wrote into the queue as a draft (or already approved) for a platform: x, linkedin, bluesky, threads, or any Postiz channel provider (tiktok, instagram, youtube). Publish it with publish_post. Character limits are enforced per platform. _scope: write_ Parameters: - platform (required): string - body (required): string - approve: boolean 路 true = lands approved instead of draft (still needs publish_post to go out) - media_from_content_id: string 路 uuid 路 Attach a rendered video or carousel from list_content - rationale: string Example input: `{"platform":"linkedin","body":"Three things we learned...","approve":false}` ### draft_posts Draft original posts in the workspace's voice for one platform, from a topic: a share-of-model plan item (plan_item_key from get_share_of_model), a brief, verbatim hot-match phrasings (lead_ids), or a seed post to riff on. Drafts land in the queue for review. 1 credit per run (one platform, up to 3 variants). Running the same topic on the same platform again is free, and seed_post_id (more like this) is always free. A run that produces nothing refunds itself. Returns a job_id. _scope: spend 路 spends credits (draft) 路 async, returns job_id_ Parameters: - platform (required): string - count: integer 路 1..3 - brief: string - plan_item_key: string - lead_ids: array - seed_post_id: string 路 uuid - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"platform":"linkedin","plan_item_key":"post:...","count":2}` ### review_post Approve, reject, or edit a queued post. Rejections and edits can carry feedback, which becomes a standing rule for future drafts. Approving does not publish, call publish_post for that. _scope: write_ Parameters: - post_id (required): string 路 uuid 路 Post id from list_posts - action (required): string 路 one of: approve | reject | edit - body: string 路 New text, for action=edit - feedback: string 路 Why, kept as a standing rule for the drafter Example input: `{"post_id":"","action":"reject","feedback":"Too salesy, never open with the product name"}` ### publish_post Publish a queued post under the workspace's name through the connected account for its platform (Postiz for its channels). Marks it approved by you. Optional schedule_for (ISO) queues it for later. Free, but requires approved: true and a post-scoped key. Returns a job_id unless scheduled. _scope: post 路 async, returns job_id_ Parameters: - post_id (required): string 路 uuid 路 Post id from list_posts - schedule_for: string 路 date-time - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"post_id":"","approved":true,"max_credits":0}` ### post_reply Reply to a matched conversation on its platform, under the workspace's connected account (X, LinkedIn comment, Bluesky). Pass the text, or omit it to send the first draft_reply variant. Marks the conversation contacted and records the outreach. Free, requires approved: true and a post-scoped key. _scope: post 路 async, returns job_id_ Parameters: - lead_id (required): string 路 uuid - text: string - dry_run: boolean 路 true = estimate only, nothing runs and nothing is charged - approved: boolean 路 Required to run a capability that spends credits. Omit it to get a dry-run estimate first. - max_credits: number 路 Ceiling for this call. The call fails with budget_exceeded when the estimate is higher. Example input: `{"lead_id":"","text":"Same here, we moved off X last year because...","approved":true,"max_credits":0}` ### get_posting_plan The drafting cadence per platform: how often Oetzi drafts original posts (off | daily | every2d | weekly), how many per run, whether autopilot publishes them without a review, and when it last ran. _scope: read_ No parameters. ### set_posting_plan Set the drafting cadence for one platform. Each run is 1 credit (1 to 3 posts) and drafts land in the queue for review. autopilot: true publishes them without a review, spread across the day, only when an account can publish there. Off by default. _scope: write_ Parameters: - platform (required): string - cadence (required): string 路 one of: off | daily | every2d | weekly - per_run: integer 路 1..3 - autopilot: boolean Example input: `{"platform":"linkedin","cadence":"every2d","per_run":1,"autopilot":false}` ## Jobs ### get_job State of a background job: status (running | done | error), phase (queued | running | done | error), progress when the worker reports it, the result once done, and the error otherwise. _scope: read_ Parameters: - job_id (required): string 路 uuid 路 job_id returned by an async capability ### wait_job Block until a job finishes, up to 25 seconds per call, then return its state. Call it again while status is still running. Cheaper than polling get_job in a loop. _scope: read_ Parameters: - job_id (required): string 路 uuid 路 job_id returned by an async capability - timeout_ms: integer 路 1000..25000 路 Default and maximum 25000 ## Events ### list_events Events in this workspace after a cursor: conversations delivered (lead.delivered, lead.hot), people discovered, posts published, share-of-model checks, jobs completed, outreach recorded. Returns up to 200 in id order. Start with since=0, then pass the last id you saw. Call with no since to get the current cursor. _scope: read_ Parameters: - since: integer 路 0..9007199254740991 路 Exclusive cursor (event id). Omit to learn the latest id. - types: array 路 Any of: lead.delivered, lead.hot, person.discovered, post.published, geo.check.completed, job.completed, outreach.recorded - limit: integer 路 1..200 Example input: `{"since":0,"types":["lead.hot"],"limit":50}` ### wait_events Block until at least one event after the cursor lands, up to 25 seconds per call, then return the batch. Loop on it for a push-style agent without running a webhook endpoint. _scope: read_ Parameters: - since (required): integer 路 0..9007199254740991 路 Exclusive cursor (event id) - types: array 路 Any of: lead.delivered, lead.hot, person.discovered, post.published, geo.check.completed, job.completed, outreach.recorded - limit: integer 路 1..200 - timeout_ms: integer 路 1000..25000 Example input: `{"since":120,"types":["lead.hot","person.discovered"]}` ### list_webhooks Webhook subscriptions in this workspace: URL, event types, active flag, last status, failure count. Secrets are never returned after creation. _scope: read_ No parameters. ### subscribe_webhook Register an https URL to receive events as signed JSON POSTs (header X-Oetzi-Signature = sha256 HMAC of the body with the returned secret). Pass event_types or ["*"] for everything. A subscription is disabled after 20 consecutive failures. The secret is returned once. _scope: write_ Parameters: - url (required): string 路 uri - event_types (required): array - label: string Example input: `{"url":"https://example.com/oetzi","event_types":["lead.hot","person.discovered"]}` ### unsubscribe_webhook Remove a webhook subscription by id. _scope: write_ Parameters: - id (required): string 路 uuid ## Semantics worth knowing - Credits meter work (sweeps, share-of-model questions, renders, people pulls), never conversations. Reading, drafting, and marking are free. - Statuses are shared with the human inbox and Slack, never act on a conversation whose status is not "new". - Oetzi publishes only through capabilities with the post scope, on accounts a human connected, and only with approved: true.