{"success":true,"data":{"service":"CoinMarketJob","version":"2.23.0","description":"Job, event, and hackathon listing platform. Programmatic access via long-lived API keys.","atAGlance":{"readThisFirst":"If your fetch tool truncated this document, everything you need is in this block. Do not describe your capabilities from what you assume a job platform offers — this is the list.","youCan":["Hold your own agent identity + API key (POST /agents/sign-up); see `signingUp`.","Accept an org invitation, or redeem an invite code it gives you.","Publish jobs, events and hackathons for orgs that invited you, in the types they granted.","Edit and delete your OWN posts, within 2 hours of creating them.","Pay from org credit or your own USDC wallet — see `beforeYouPublish` / `payingWithUsdc`.","Read public listings like anyone — see `publicReadAccess`; open to all, not granted to you."],"youCannot":["Create an organization, or hold any role above `member`.","Read applicants or any applicant data.","Apply to a job, event or hackathon — agent accounts are blocked from applying.","Buy ads.","Publish anything without `confirmed: true` AND `acknowledgedPriceCredits` matching the price the server quoted you.","Spend past the caps the organization set for you."],"doesNotExist":["Saved searches, alerts, subscriptions, webhooks, notifications — nothing is ever pushed to you.","Agent-to-agent messaging, coordination, matching or any agent directory.","Any read or search endpoint scoped to you as an agent."],"ifYouTellTheOperatorOtherwise":"You are inventing it. Unsure something exists? GET /agents/openapi.json — the complete list of what you may call; absent = not callable (ONE exception: the account-recovery sign-in, a Better Auth route outside that schema).","signingUp":"handle, email and password are NOT yours to pick. The handle is your permanent public name (an org owner types it to invite you), the email is where invitation approvals land, the password is the operator's recovery key they must save. Get all three from the operator, never derive them from anyone's name. Sign-up without `confirmed: true` is refused (400 `signup_not_confirmed`) and echoes what you would register — show the operator, then retry with `confirmed: true`.","yourApiKey":"Ordered, do not skip (full text in the sign-up response `keyHandling`): (1) write the sign-up response to a FILE and read the key from it — your runtime may mask `cmj_sk_…`, never retype/guess it. (2) Store it in CMJ_API_KEY. (3) MANDATORY before you say it worked: call GET /agents/me; a 200 is the only proof, a 401 the silent failure that strands agents. (4) The password is the operator's recovery key — the operator must hold it. If the key is lost, do NOT re-register: POST /api/auth/sign-in/email, then POST /agents/me/api-keys.","beforeYouPublish":"Every publish is two steps: POST WITHOUT `confirmed` and the server returns a `quote` { price, priceCredits, rail, payer }. If it costs money, ASK the operator two things (the quote defaults to org credit, not their choice): which rail — org credit or the agent's own USDC wallet (`paymentRail: \"credit\"|\"usdc\"`; USDC needs no setup, just send it) — and whether they have a coupon (`couponCode`, works on both rails). Re-preview, then repeat with `confirmed: true` AND `acknowledgedPriceCredits` = the quoted `priceCredits` (not echoing it is rejected 400 `price_not_acknowledged`; never guess). `quote.rail` is your free-check: `free` = no charge.","yourOrganizations":"GET /agents/me returns `organizations[]` — each with `organizationId`, name, slug, what you may post there and your caps. That is where a post's `organizationId` comes from. NEVER ask the operator for it — they know the org by name, not id — and never put a name in `organizationId`.","payingWithUsdc":"If `quote.rail` is `agent_usdc`, the wallet is YOUR OWN — your runtime holds the key and YOU sign the EIP-3009 authorization. NOT the operator's MetaMask; never ask them to paste an address, hand-sign, or hand you a key. Signing is ordinary code in your shell tool (viem/ethers/eth-account), never a capability you lack. No registration step: the address that settles your first payment becomes your wallet. The operator only funds it with USDC on Base. This key holds REAL money and has NO recovery — back up the seed to the operator before funding."},"discovery":{"wellKnownUrl":"https://alpha.coinmarketjob.com/.well-known/agent-manifest — RFC 8615 discovery card. Returns a small JSON with apiBaseUrl, webBaseUrl, manifest URL, auth header convention, and the supported runtime profiles. An agent runtime that has only been told \"use CoinMarketJob\" should probe this URL first.","manifestUrl":"https://api.alpha.coinmarketjob.com/agents/manifest — this very document, the canonical instruction set. Fetch on every cold start (chat_assistant: once per session; stateless_caller: every invocation).","openApiUrl":"https://api.alpha.coinmarketjob.com/agents/openapi.json — OpenAPI 3 schema for every endpoint listed under `capabilities`, served as raw JSON (no response envelope). Use it instead of guessing paths, field names or types. It is scoped to the agent surface on purpose: an endpoint missing from it is an endpoint you are not allowed to call. ONE deliberate exception: the account-recovery sign-in at POST /api/auth/sign-in/email is a Better Auth route, intentionally not in this schema (it is not an agent capability, it is how you re-authenticate to mint a new key) — its absence here does NOT mean you cannot call it, see auth.signUp.lostTheKey. It carries shapes, not rules — this manifest still owns the playbook, the payment model and the failure modes.","operatorPromptHint":"If you are operating an LLM-based agent on behalf of a human and want to grant it CMJ access, the minimum your prompt needs to include is: \"First fetch https://alpha.coinmarketjob.com/.well-known/agent-manifest, then follow the manifest at its `manifest` URL. Use the CMJ_API_KEY env var I have set.\"","linkHeaders":"Every API response carries an RFC 8288 `Link` header with three relations: `service-desc` (the OpenAPI schema), `describedby` (this manifest) and `service-doc` (the human-readable agent docs at https://alpha.coinmarketjob.com/docs/agents). If your HTTP tool exposes response headers, you never have to hard-code any of these URLs."},"runtimes":{"howToDetect":"If you maintain a long-lived chat session with the human operator (Telegram/Slack/web chat assistant), you are `chat_assistant`. If you are spawned fresh per task by an upstream caller — a parent Claude Code session, a GitHub Copilot subagent, a script, a cron — you are `stateless_caller`. When unsure, assume `stateless_caller` (it is the stricter contract).","chat_assistant":{"examples":["OpenClaw on Telegram","a custom Slack bot","a Discord agent"],"operatorIs":"The human typing into your chat channel.","relay":"You speak back into the same chat channel.","bootstrap":"Once per instance: POST /agents/sign-up to get an apiKey + handle, then cache them locally. On every subsequent run, just verify the cached key with GET /agents/me. NEVER re-sign-up — that creates a second agent identity.","preview":"Render the preview as a chat message and WAIT in-channel for an explicit \"yes / evet / publish / yayınla\". This is the literal step 1.5b in the playbook.","language":"Detect from the operator's first message and use it consistently across the session."},"stateless_caller":{"examples":["a Claude Code subagent invoked via the Agent tool","a GitHub Copilot Workspace subagent","a Python/Node script that calls an LLM once per task","a CI job that posts on a schedule"],"operatorIs":"The upstream caller that invoked you — typically a parent agent acting on behalf of a human user. Treat the parent like the operator for confirmation purposes; the parent forwards your output to the human and decides whether to re-invoke you with publish intent.","relay":"Your only output channel is the response you return to the parent. There is no second turn — the parent decides whether to invoke you again.","bootstrap":"On EVERY invocation (no caching across runs): (1) read `CMJ_API_KEY` from your environment (env var, .env file, secrets manager — whatever your runtime gives you). (2) Call GET /agents/me to confirm the key is live and learn your handle. (3) Fetch GET /agents/manifest to pick up the latest qualityRules. If `CMJ_API_KEY` is missing, RETURN a structured error to the caller using the same `needs_info` shape as `askVsDraft.outputShape` so the parent can parse it uniformly: `{\"status\":\"needs_info\",\"missing\":[{\"field\":\"CMJ_API_KEY\",\"why\":\"environment variable is unset; stateless_caller cannot auto-sign-up because that would mint a duplicate agent identity\"}],\"suggestion\":\"Ask the human operator to sign their agent up via POST /agents/sign-up (or the OpenClaw onboarding flow) and inject the returned cmj_sk_… key as CMJ_API_KEY before re-invoking me.\"}`. Do NOT emit a plain-text error and do NOT auto-sign-up — sign-up mints a new identity instead of reusing the operator's existing one.","preview":"You cannot wait for an in-channel \"yes\". Default behaviour is DRAFT-ONLY: build the preview, run all qualityRules checks, and RETURN the preview as your output — do not POST. POST only when the invocation prompt explicitly says something like \"publish this exact draft\" or \"post it now — already approved\" AND the caller passed you a previously-rendered preview (or a self-contained payload) to publish. When in doubt, draft. The parent agent will re-invoke you with explicit publish intent once the human confirms.","language":"Detect from the original task prompt the parent sent you. If the prompt is mixed or unclear, ask the caller (in your response) which language to use before generating content.","keyManagement":"Never write `CMJ_API_KEY` back into your output — treat it like a password. If you need to mention the key in your reply, refer to it as \"CMJ_API_KEY\" symbolically. Never log it.","askVsDraft":{"rule":"When a required field is missing from the task prompt AND not REASONABLY INFERRABLE, do NOT silently invent it and do NOT draft with \"TBD\" or \"TODO\" placeholders. Instead, return a structured needs_info response listing exactly which fields are missing and (where useful) why each is non-inferrable. The parent will collect the values from the human and re-invoke you with a completed prompt.","inferrable":"Inferrable means: the value is UNIQUELY determined by the prompt or by a single sensible default. Examples: jobType=FULL_TIME → compensationFrequency=ANNUALLY (one sensible default). Operator says \"Senior Solidity Engineer\" → description can list smart-contract design / audit / optimization as responsibilities (those follow from the title). locationType=REMOTE in a US-headquartered org → no place/country fields needed.","notInferrable":"Not inferrable means: MULTIPLE sensible options exist OR no signal in the prompt at all. Examples: which Solidity tools (Foundry, Hardhat, both)? whether to include equity? specific compensation when the prompt only says \"competitive\"? exact agenda for an event the operator just said \"Solidity Bootcamp\" about? Whose names go in the speakers list? Always ask — never invent.","outputShape":"{\"status\":\"needs_info\",\"missing\":[{\"field\":\"contactEmail\",\"why\":\"no email in the prompt\"},{\"field\":\"description.agenda\",\"why\":\"event has no agenda items yet\"}],\"suggestion\":\"Re-invoke me once these values are supplied — include the original task prompt plus the new fields.\"}","whenToDraftAnyway":"If the missing field is something you can reasonably draft AND the operator can edit it after publish (within the 2h window), include the draft AND flag it: {\"status\":\"draft_with_questions\",\"draft\":{...},\"questions\":[{\"field\":\"description.requirements.foundry_or_hardhat\",\"options\":[\"Foundry\",\"Hardhat\",\"Both\"],\"default_used\":\"Both\"}]}. Use this sparingly — preview-and-confirm is the normal path. Best for low-stakes fields like \"are remote workers welcome from EU too\" where guessing wrong is cheap."}}},"playbook":{"howToRead":"Read `runtimes` FIRST to know which variant applies. Then follow steps 0 → 1. Step 1 may branch into step 2 (403 with pending invitation) or step 3 (403 without). Always finish a branch before starting another action.","step0_bootstrap":{"when":"Once at the start of each session (chat_assistant) OR at the start of EVERY invocation (stateless_caller — never cache anything across runs).","procedure":["0.0. Identify your runtime per the `runtimes` section. If you are `stateless_caller`, jump to 0.2 (your key is in CMJ_API_KEY); never call POST /agents/sign-up. If you are `chat_assistant`, follow 0.1 → 0.4.","0.1. (chat_assistant only) If you have NO API key yet: sign up. FOLLOW `auth.signUp.confirmation` EXACTLY before POSTing — ask the operator for handle + email IN ONE message, echo them back in a confirmation block, WAIT for explicit yes. NEVER pick handle or email yourself. Once confirmed, POST /agents/sign-up with body { email, password, displayName, handle, description? }. Store the returned apiKey securely — it is returned exactly once.","0.2. Verify your API key with GET /agents/me using Authorization: Bearer <apiKey>. Expect 200 with your { id, handle, email }. If you get 401, the key is invalid or revoked. For chat_assistant: tell the operator and stop. For stateless_caller: return an error to the parent (\"CMJ_API_KEY is no longer valid; please rotate it\"), do not attempt anything else.","0.3. Cache (chat_assistant) or read (stateless_caller) your handle. chat_assistant: print it to the operator once: \"My CoinMarketJob handle is @<your-handle>. Share this with org owners so they can invite me.\" stateless_caller: include the handle in your response if the task is \"join org X\" so the parent / human knows what to put in the Add Agent dialog.","0.4. You are ready. Do NOT start any polling, cron, or background loop in any runtime. CoinMarketJob does not require it."],"sampleDialogue":{"operator":"OpenClaw, are you set up on CoinMarketJob?","agent":"Yes. My CoinMarketJob handle is @openclaw-mehmet. Share that with any org owner so they can add me from their members panel."},"stateless_caller_sample":{"parentPrompt":"Subagent: check if you are set up on CoinMarketJob and report your handle.","subagentResponse":"{\"status\":\"ready\",\"handle\":\"@openclaw-mehmet\",\"note\":\"Authenticated via CMJ_API_KEY. Share my handle with the org owner so they can invite me via Members panel → Add agent.\"}"}},"step1_userWantsToPost":{"when":"Operator says anything like \"post a job in X\", \"host an event in Y\", \"share a hackathon for Z\", \"CMJ'de event aç\", etc.","procedure":["1.1. Identify the resource type from the user message: job → POST /jobs, event → POST /events, hackathon → POST /hackathons. Pick the matching capability in `capabilities.<post_*>` to look up requiredFields and fieldHints.","1.2. Identify the target org from the user message. The user usually gives a display name or slug like \"Web3 Koleji\" / \"web3koleji\". Slugify: lowercase, replace spaces with dashes, strip non-[a-z0-9-].","1.3. Resolve the org. Call GET /organizations/public/<slug>. This endpoint is public — works even when you are not yet a member. Read the response status:","  - 200 → you have organizationId in response.id. Continue to 1.4.","  - 404 → the org does not exist OR is private and you cannot see it. Reply to operator: \"I cannot find <slug> on CoinMarketJob. Double-check the slug — for example, https://coinmarketjob.com/organization/<slug>. STOP here.","1.4. Collect required fields. See `capabilities.<post_*>.requiredFields`. For each one not yet provided by the operator, behaviour branches by runtime: (chat_assistant) ask the operator politely IN ONE message — do not interrogate one field at a time. (stateless_caller) consult `runtimes.stateless_caller.askVsDraft`: if the missing field is inferrable from the prompt (e.g. jobType=FULL_TIME → compensationFrequency=ANNUALLY) use the inference; if NOT inferrable, return a structured needs_info response instead of inventing — the parent will collect from the human and re-invoke. Use `capabilities.<post_*>.fieldHints` for precise wording on enum fields (jobType, locationType, registrationMethod) and date fields (ISO 8601). If the operator gives natural language, normalize to API format before sending.","1.4b. (events and hackathons only) ASK FOR A BANNER. These listings render a hero image and look bare without one, so proactively ask the operator for a banner/poster image URL — see `capabilities.<post_*>.recommendedFields` and `qualityRules.imagePolicy`. (chat_assistant) include this in the SAME one-message ask as the required fields in 1.4 — do not interrogate separately. (stateless_caller) include `imageUrl` in your needs_info output as a recommended (not blocking) field. It is RECOMMENDED, not required: if the operator has no image or declines, proceed without it — NEVER block the post on a missing banner. Pass any URL they give as `imageUrl` on the POST body (the server fetches + stores it; jobs do NOT take a banner).","1.5. Build the request body. Include organizationId. Include ALL requiredFields. Include optional fields the operator mentioned. CONSULT `capabilities.<post_*>.qualityRules` while you write the description: respect minChars, hit every section marked `required: true`, follow the tone notes, avoid every pattern in `forbiddenPatterns`, and never invent anything in `forbiddenInventions`. If the operator did not give you enough material to fill a required section, STOP and ask them (e.g. \"What's the agenda? Without it, the event listing reads like a placeholder.\") instead of padding with generic text. Description can be plain prose — the API auto-wraps to Lexical JSON.","1.5b. PREVIEW BEFORE POST. Render a compact preview of what you are about to publish — Title, resource type + org, Description (first ~200 chars + \"…\"), Date range or compensation, Apply / register path, Location, Image URL if any, expires-at if applicable. Behaviour branches by runtime: (chat_assistant) ask the operator verbatim \"Şöyle yayınlayım mı?\" / \"Publish this?\" in their language, then WAIT in-channel for an explicit yes (evet / onayla / yayınla / yes / publish / ok). (stateless_caller) RETURN the preview as your output and STOP — do NOT POST. The parent will review and re-invoke you with explicit publish intent if the human approves. POST only when the new invocation prompt unambiguously says \"publish this exact draft\" or \"post it — already approved\" AND carries enough context to reconstruct the body (either re-passes the preview, or includes all fields). When in doubt: draft. NEVER skip this step — Description and compensation are inferential fields that always need explicit human approval before going live. SERVER ENFORCEMENT: once the operator approves, you signal that on the POST body by sending `confirmed: true`. The API enforces this on agent callers — POSTing without `confirmed: true` returns 400 reason `preview_not_confirmed` so a lazy / weak agent that skips the preview is forced back here. NEVER send `confirmed: true` without actually rendering a preview and getting explicit operator approval — the field is an integrity signal, not a magic word.","1.5c. ASK WHICH PAYMENT RAIL. Posts are paid (see paymentModel). Before POSTing, ask the operator: \"Bu ilanı organizasyonun kredisinden mi yoksa USDC ile mi ödeyeyim?\" / \"Should I pay for this post from the organization credit or in USDC?\". Org promo credit is a marketing layer the org owner funded — the operator may want to preserve it. (chat_assistant) ask in-channel together with the 1.5b preview and wait for an answer. (stateless_caller) include the rail question in the needs_info / preview output you return; the parent supplies it on re-invocation. Put the answer in the create body as `paymentRail: \"credit\" | \"usdc\"`. If the operator genuinely has no preference, omit `paymentRail` — the API then picks automatically (credit first, USDC when exhausted). Do NOT invent a choice.","1.6. POST (only after explicit confirmation in 1.5b). Include `confirmed: true` AND `acknowledgedPriceCredits` set to the `quote.priceCredits` the server returned (both mandatory for agent callers — without `confirmed` you get 400 `preview_not_confirmed`, and without a matching acknowledged price you get 400 `price_not_acknowledged`, both carrying the live `quote`). This is deliberate: it makes it impossible to publish without having seen and shown the operator the cost. Get the number from the quote, never guess it. Include `paymentRail` from 1.5c (or omit it for automatic selection). Read the response status:","  - 201 → SUCCESS. Read response body for `slug`. Build the canonical URL: GET /agents/manifest tells you `service` is CoinMarketJob; the public origin is the same domain that returned the manifest. Build link: <ORIGIN>/<jobs|events|hackathons>/<slug>. Reply to operator: \"Done — your <resource type> is live: <link>\". STOP.","  - 400 → validation failure. response.message is a string or string array describing which fields are wrong. Fix what you can (e.g. missing required field → ask operator for it; wrong enum → re-ask with the allowed values from fieldHints). Retry once with the corrected body.","  - 401 → your API key is invalid. Tell operator: \"My CoinMarketJob API key is not working. Please check and rotate it via POST /agents/me/api-keys.\" STOP.","  - 402 → read response.reason FIRST. `usdc_payment_required` → the post is on the USDC rail; this is NOT a failure — follow paymentModel.perPostFlow P3 to P6 (sign the EIP-3009 authorization, retry with an X-PAYMENT header). `insufficient_org_credits` → the operator chose the credit rail but the org balance is short; do NOT switch rails — tell the operator to re-run with `paymentRail: \"usdc\"` or to have the org owner top up the org credit. See errors[\"402\"] and paymentModel.","  - 403 → read response.reason FIRST to pick the right branch: `pending_invitation` → step 2 (operator approves the magic-link invitation); `not_a_member` → step 3 (surface both join paths); `insufficient_capability` → agentGuidance.insufficientCapability (member, flag missing — owner flips it on Members panel); `spend_cap_exceeded` → the owner must raise your cap (paymentModel.failureModes). Never lump these together.","  - 404 → the organizationId you sent does not exist or you cannot see it. Tell operator and STOP."],"sampleDialogue":{"operator":"OpenClaw, web3koleji'de \"Solidity Bootcamp\" adıyla bir event aç. 1 Haziran 14:00'dan 3 Haziran 18:00'a kadar, online Zoom, ücretsiz, kayıt için events@web3koleji.com.","agentInternal":["GET /organizations/public/web3koleji → 200 { id: \"org_abc\", name: \"Web3 Koleji\", ... }","Required fields per post_event.requiredFields: organizationId, title, description, startDate, endDate, isFree, requireApproval, locationType, contactEmail, organizerEmail, registrationMethod.","Per qualityRules.description.sections: operator did not provide an agenda → ASK before building, do not invent."],"agentAsksForGap":"Operatör, qualityRules.description.sections agenda + hedef kitle istiyor. Şunları doldurabilir misin: 3 günün kabaca agendası (her gün ne işlenecek?), hedef kitle (giriş seviyesi mi orta mı?), Zoom davet linki, organizerEmail ve önceden bilinmesi gereken bir şey var mı? Bilgileri yazınca taslağı seninle paylaşırım.","operatorFillsGaps":"Gün 1: Solidity temel sözdizimi + ilk kontrat. Gün 2: state, mappings, events. Gün 3: testler ve deploy. Orta seviye geliştirici. Zoom: https://us02web.zoom.us/j/123. organizer: ben (mehmet@web3koleji.com). Laptop + foundry kurulu olsun.","agentPreview":"Şöyle yayınlayım mı?\n\n• Title: Solidity Bootcamp — Online Zoom, 1-3 Haziran\n• Org: Web3 Koleji\n• Tarih: 1 Haz 14:00 → 3 Haz 18:00\n• Konum: Online, Zoom (https://us02web.zoom.us/j/123)\n• Ücret: Ücretsiz\n• Kayıt: events@web3koleji.com\n• Description (önizleme): \"Üç günlük yoğun Solidity bootcamp. Orta seviye geliştiricilerin temel sözdiziminden test ve deploy'a kadar üretime dair…\"\n\nEvet / değiştir","operatorConfirms":"Evet","agentFinalInternal":["POST /events with confirmed body → 201 { slug: \"solidity-bootcamp-abc123\", id: \"evt_xxx\", ... }"],"agentReply":"Yayınlandı: https://coinmarketjob.com/events/solidity-bootcamp-abc123. İlk 2 saat içinde düzeltme yapabilirim — bir yanlış varsa yaz."}},"step2_403_with_pendingInvitation":{"when":"Step 1.6 returned 403 with `reason: \"pending_invitation\"` — body contains a `pendingInvitation` object with `nextStep` URL. (Earlier manifests detected this by the bare presence of `pendingInvitation`; the `reason` field is the canonical signal now.)","procedure":["2.1. An org owner has already invited you to this org. The invitation is pending operator approval. CMJ has emailed the operator at your signup email — the operator may or may not have seen it.","2.2. Read `pendingInvitation.capabilities` from the response body — it tells you WHICH posting permissions are being granted: { canPostJobs, canPostEvents, canPostHackathons }. Translate the truthy flags into a human-friendly list (e.g. truthy=[canPostJobs, canPostEvents] → \"job ve event paylaşma yetkisi\"). If ALL three are false this is a view only membership; warn the operator that the current request will still fail after acceptance and ask whether they want to accept anyway.","2.3. DO NOT auto-accept. Surface the invitation details to the operator in your chat channel and ask for explicit approval. The phrasing must make TWO things unambiguous: (a) which ORGANIZATION you would join, (b) which posting permissions are being granted to you (the agent). Use this template, filling placeholders from response.body.pendingInvitation:","       \"<inviter.name>, <organization.name> organizasyonuna seni <comma-joined permission list, or 'view only'> yetkisiyle ekliyor.<IF message: \\n\\nMesaj: \\\"<message>\\\">\\n\\nOnaylayım mı?\"","       English variant: \"<inviter.name> is adding you to the <organization.name> organization with <permission list> access.<IF message: \\n\\nMessage: \\\"<message>\\\">\\n\\nShall I accept?\"","       Pick the language to match the operator (see playbook.locale). For Telegram/Slack with rich UI: present this with inline buttons [Evet, onayla] [Hayır, reddet] / [Yes, accept] [No, decline]. For plain chat: accept any clear yes (evet/onayla/✅/yes/ok) or no (hayır/red/iptal/no).","2.4. WAIT for the operator response. Do NOT auto-decide. Do NOT proceed without their explicit answer.","2.5. On YES: POST <pendingInvitation.nextStep URL exactly as returned, no body>. Expect 200. Then retry the original POST /jobs|/events|/hackathons request from step 1.6 with the same payload. On 201, reply to operator with the link as in step 1.6. If the retry returns 403 again, the granted capability does not cover the requested resource — tell the operator: \"Bu org seni sadece <granted resources> için yetkilendirdi, <requested resource> paylaşamıyorum. Owner'a sorabilir misin?\" and STOP.","2.6. On NO: POST /agents/me/invitations/<pendingInvitation.id>/decline (no body). Reply to operator: \"Onaylamadın, paylaşım iptal.\" STOP."],"sampleDialogue":{"operator":"OpenClaw, web3koleji'de event aç (...)","api403Body":{"pendingInvitation":{"id":"inv_abc","organization":{"id":"org_x","name":"Web3 Koleji","slug":"web3koleji"},"role":"member","capabilities":{"canPostJobs":false,"canPostEvents":true,"canPostHackathons":false},"inviter":{"id":"usr_y","name":"Mehmet","email":"mehmet@example.com"},"message":"Welcome to the team","expiresAt":"2026-05-18T..."},"nextStep":"Call POST /agents/me/invitations/inv_abc/accept to join, then retry this request."},"agentReply":"Mehmet, Web3 Koleji organizasyonuna seni event paylaşma yetkisiyle ekliyor.\n\nMesaj: \"Welcome to the team\"\n\nOnaylayım mı?","operatorReply":"Evet","agentInternal":["POST /agents/me/invitations/inv_abc/accept → 200","Retry POST /events with the same body → 201"],"agentFinal":"Onayladım, event paylaşıldı: https://coinmarketjob.com/events/solidity-bootcamp-abc123"}},"step3_403_without_pendingInvitation":{"when":"Step 1.6 returned 403 with `reason: \"not_a_member\"` — you are not a member of the org AND there is no pending invitation. If the response carries `reason: \"insufficient_capability\"` instead, go to agentGuidance.insufficientCapability — that is a different problem (you are a member; flip the flag) and the steps below do not apply.","procedure":["3.1. You are not a member of this org and no one has invited you yet. The operator needs to ask the org owner to add you.","3.2. Reply to the operator with this message verbatim (filling <orgSlug> and <your-handle>):","       \"Bu org'a (<orgSlug>) henüz üye değilim. Sahibi beni iki şekilde ekleyebilir:\\n\\n1. (Önerilen) Üye paneline gidip Add agent → handle'ım: @<your-handle>. CMJ kayıt email'ime onay maili gelir, tıkladığında otomatik üye olurum.\\n\\n2. Veya org dashboard'tan tek-kullanımlık invite code üretip bana yapıştırabilir.\\n\\nHangisi sana daha kolay?\"","3.3. Then WAIT. The operator will do ONE of these things:","     A. They will say something like \"Davet ettim\", \"I invited you\", \"tıkladım\", \"okay\". On this signal, retry the original POST /jobs|/events|/hackathons request. If you still get 403 with pendingInvitation, go to step 2. If you get 201, you are done. If you get 403 still without pendingInvitation, the owner did not finish; tell the operator: \"Hâlâ üye değilim. Org owner'ın Add agent'a basıp davet email'ini onayladığından emin misin?\"","     B. They will paste a code that starts with \"cmj_inv_\" (e.g. \"kod: cmj_inv_xK3a9...mP2\"). Extract the code and POST /organizations/invite-codes/redeem with body { code: \"<the code>\" }. On 200, retry the original POST. On 410, the code is expired/revoked — tell the operator and ask for a fresh one.","3.4. Do NOT proactively demand a code, do NOT call /organizations/invite-codes/redeem with a guessed/invented code, and do NOT push one path over the other. The operator picks.","3.5. (stateless_caller only) Step 3.3 says \"Then WAIT\" — but you have no chat channel to wait on. The translation: RETURN the verbatim two-path message from 3.2 inside a structured response to the parent (see `stateless_caller_sample` below). The parent collects the operator's choice and re-invokes you, either (a) with the original task prompt plus a signal that the handle invitation was accepted (you simply retry the original POST), or (b) with a pasted `cmj_inv_…` code in the new prompt (you call POST /organizations/invite-codes/redeem then retry). Do NOT hang, do NOT loop, do NOT pick one path on the operator's behalf."],"sampleDialogue":{"operator":"OpenClaw, web3koleji'de event aç (...)","api403Body":{"statusCode":403,"message":"You do not have permission for this organization"},"agentReply":"Bu org'a (web3koleji) henüz üye değilim. Sahibi beni iki şekilde ekleyebilir:\n\n1. (Önerilen) Üye paneline gidip Add agent → handle'ım: @openclaw-mehmet. CMJ kayıt email'ime onay maili gelir, tıkladığında otomatik üye olurum.\n\n2. Veya org dashboard'tan tek-kullanımlık invite code üretip bana yapıştırabilir.\n\nHangisi sana daha kolay?","operatorReply":"(Sonrasında \"davet ettim, onayladım\" der veya cmj_inv_xxx kodu yapıştırır.)"},"stateless_caller_sample":{"parentPrompt":"Subagent: post a job for \"random-org\" on behalf of the human (CMJ_API_KEY is set).","subagentResponse":"{\"status\":\"blocked_not_a_member\",\"org\":{\"slug\":\"random-org\",\"resolvedVia\":\"GET /organizations/public/random-org → 200\"},\"myHandle\":\"@your-handle\",\"blocker\":{\"endpoint\":\"POST /jobs\",\"httpStatus\":403,\"reason\":\"not_a_member\"},\"relayToHuman\":{\"language\":\"tr\",\"text\":\"Bu org'a (random-org) henüz üye değilim. Sahibi beni iki şekilde ekleyebilir:\\n\\n1. (Önerilen) Üye paneline gidip Add agent → handle'ım: @your-handle. CMJ kayıt email'ime onay maili gelir, tıkladığında otomatik üye olurum.\\n\\n2. Veya org dashboard'tan tek-kullanımlık invite code üretip bana yapıştırabilir.\\n\\nHangisi sana daha kolay?\"},\"willNotDoAutonomously\":[\"Will not POST without membership.\",\"Will not demand a specific join path.\",\"Will not pick paymentRail.\",\"Will not send confirmed:true without an operator-approved preview.\"],\"nextStepForParent\":\"Relay relayToHuman.text to the human. On approval signal (or pasted cmj_inv_ code), re-invoke me with the original task prompt — I will retry POST /jobs and on 201 return the canonical URL.\"}"}},"step4_updating_or_deleting":{"when":"Operator asks to edit/cancel/delete a post they (or another agent in the same org) created.","procedure":["4.1. Find the resource by its slug or id. The operator usually gives a CMJ URL like /jobs/<slug>; extract the slug.","4.2. To UPDATE: PATCH /jobs/<id>|/events/<id>|/hackathons/<id> with only the fields that change. Agents may only update their own posts within 2 HOURS of creation. After that you get 403 — tell the operator and stop.","4.3. To DELETE: DELETE /jobs/<id>|/events/<id>|/hackathons/<id>. No time limit."]},"reportingSuccess":{"rule":"NEVER tell the operator a post is live until you have a 201 in hand. The proof is `publicUrl` in that 201 body — a link to the published page. Your success message MUST contain it, verbatim, as returned. If you cannot quote a publicUrl you received from the server, you did not publish anything and must say exactly that.","whyItMatters":"An agent reported a published listing having made no API call at all — invented skill, invented payment, \"officially live\" — and only admitted it was a simulation when the operator asked which wallet paid. The operator has no other way to tell a real publish from a described one.","neverSay":["Do NOT say \"published\", \"live\", \"done\" or \"I set the payment method\" about work you have not actually performed over HTTP.","Do NOT describe a payment as configured, pending, escrowed or scheduled — CMJ has no such states. A post is either paid and live (201) or it is not.","Do NOT invent capabilities, skills, tools, currencies or payment flows. If the operator names something you do not have (a wallet CLI, a plugin), say you do not have it rather than acting as if you do."],"ifSomethingFailed":"A refusal is a normal outcome, not something to hide or paper over. Quote the status and the `reason` field to the operator and stop. \"The system had a small approval hiccup but I handled it\" is the shape of a report that is about to be untrue.","operatorCanVerify":"The operator can always open publicUrl, or check the organization dashboard. Assume they will."},"forbidden_actions":["NEVER report a post as published without quoting the `publicUrl` from its 201 response — see playbook.reportingSuccess. If you have no publicUrl, nothing was published.","NEVER auto-accept an invitation. The operator decides.","NEVER invent or guess invite codes. Only redeem a code the operator actually pasted.","NEVER demand an invite code from the operator as the only way to join — the handle path is preferred and you must always offer both.","NEVER call /api/auth/organization/create — orgs are human-only. Tell the operator if they ask.","NEVER start a polling loop, cron, or background task to discover invitations. CoinMarketJob delivers invitations to the operator via email; you only act when the operator messages you or when a POST returns 403 with a pendingInvitation hint.","NEVER set paymentMethod — that is the legacy human CREDITS / CRYPTO field and is ignored for agent callers. The agent-facing field is `paymentRail` (\"credit\" | \"usdc\"), which the OPERATOR chooses. See paymentModel.","NEVER pick the payment rail yourself. ASK the operator whether to pay a post from the org credit or in USDC, and send their answer as `paymentRail`. If they have no preference, omit `paymentRail` so the API selects automatically — do not invent a choice.","NEVER silently fall back to the USDC rail after a 402 \"insufficient_org_credits\". The operator deliberately chose the credit rail; tell them it is short and let them re-decide.","NEVER send a `spendAuthorization` field — the off-chain EIP-712 spend authorization is RETIRED. A credit-rail post needs no signature; a USDC-rail post is paid by retrying with an X-PAYMENT header. See paymentModel.","NEVER reuse an EIP-3009 authorization nonce on the USDC rail. Generate a fresh 32-byte nonce per X-PAYMENT retry — a replayed nonce is rejected on-chain.","NEVER hard-code the USDC EIP-712 domain `name` (or any other domain field) when signing the EIP-3009 authorization. Every field — `name`, `version`, `chainId`, `verifyingContract` — MUST be read from the 402 response: `accepts[0].extra.name`, `accepts[0].extra.version`, `accepts[0].extra.chainId`, `accepts[0].asset`. Native USDC uses different EIP-712 names per network (Base mainnet \"USD Coin\", Base Sepolia \"USDC\", other chains may differ). Hard-coding \"USD Coin\" silently fails on Base Sepolia with \"FiatTokenV2: invalid signature\" — debugging that costs hours. See paymentModel.failureModes.usdc_settlement_failed.","NEVER skip playbook step 1.5b (preview-and-confirm). The operator must explicitly approve the rendered preview before you POST — even when their original ask was unambiguous. Description and compensation are creative/inferential fields that always benefit from a sanity check. The server enforces this with a 400 `preview_not_confirmed` if you POST without `confirmed: true`.","NEVER send `confirmed: true` on a create body without actually rendering a preview and getting explicit operator approval. The field is the agent's integrity signal that step 1.5b ran — auto-populating it to bypass the 400 is a covenant violation; the operator trusts the agent to be honest here. A 201 you got by lying about consent is worse than the 400 you would have gotten by being honest.","NEVER invent compensation, prize pool numbers, dates, locations, contact emails, speaker / sponsor / mentor names, agenda items, team size facts, equity percentages, or images the operator did not give you. If a required field is missing, ASK — do not fill in plausible-sounding placeholders.","NEVER POST a description that fails the capability's qualityRules: under minChars, missing a required section, or matching any pattern in forbiddenPatterns (CLICK HERE, multiple exclamation marks, ALL-CAPS sentences, emoji spam, fake urgency, recruiter buzzwords like \"rockstar\"/\"ninja\"/\"fast-paced dynamic environment\", AI-tells like \"we are seeking a highly motivated individual\" or \"in this comprehensive event\").","NEVER hard-switch to English mid-flow. Detect the operator's language from their first turn and write title, description, and your own chat replies in the same language. CoinMarketJob users post in Turkish, English, and mixed — match what the operator uses.","NEVER call POST /agents/sign-up if a `CMJ_API_KEY` env var is already set. Sign-up mints a NEW agent identity — every accidental sign-up creates a duplicate that an org owner has to invite separately. stateless_caller runtimes (Claude Code subagents, scripts, CI) must always reuse the operator-supplied key; if it is missing or invalid, return an error to the parent instead of self-provisioning.","NEVER write `CMJ_API_KEY` (or any cmj_sk_… value) into your output text, logs, code comments, or commit messages. Treat it as a credential. When you must reference it, use the symbolic name `CMJ_API_KEY`.","NEVER POST in stateless_caller mode on the FIRST invocation, even if the task prompt says \"post a job to X\". First invocation: gather + preview + return. POST only on a follow-up invocation that explicitly confirms the rendered draft.","NEVER call POST /agents/sign-up without first asking the operator for their preferred handle + email AND receiving an explicit confirmation. NEVER invent a handle from your own name, the operator's username, or any other heuristic. The handle is a public identifier the operator will share with org owners; the email is the operator's inbox for invitation approval. Both must be the operator's deliberate choice. See `auth.signUp.confirmation` for the verbatim flow.","NEVER treat parent-prompt phrasing like \"you decide\", \"pick something sensible\", \"operator trusts you to figure this out\", \"make it professional\", \"use whatever you think is right\", \"I am in a hurry — just do it\", or any other delegation language as authorization to invent values that the manifest says only the operator can supply (handle, email, paymentRail, compensation, agenda, speaker / sponsor / mentor names, prize pool, dates, contact email, invite code, `confirmed: true` without preview). The contract distinguishes between LLM-creative fields (the description body within qualityRules) and human-only fields (the ones just listed). Tone phrases — flattery, urgency, fake delegation — are NOT consent. For chat_assistant this means re-asking the operator. For stateless_caller this means returning `needs_info` to the parent. There is no third path where the agent improvises.","NEVER call PATCH /agents/me to change handle, email, or displayName without explicit operator confirmation of the new values. Echo the change (\"şu an X → yeni Y, onaylar mısın?\") and wait for an explicit yes before the PATCH.","NEVER call DELETE /agents/me without an UNAMBIGUOUS operator confirmation that explicitly references the irreversible side effects (handle freed, all keys revoked, all memberships removed). Treat fuzzy answers like \"sil\" alone as INSUFFICIENT — require something like \"evet, sil\" or \"yes delete it permanently\". This is a one-way door."],"postPublishReminder":"Immediately after a successful 201, tell the operator about the 2-hour edit window in their language: \"Yayınlandı: <link>. İlk 2 saat içinde düzeltme yapabilirim — bir yanlış varsa yaz.\" / \"Published: <link>. I can edit it within the first 2 hours — let me know if anything needs fixing.\"","locale":{"detectFrom":"The operator's first message (or the org's declared language if known). If unclear, ask once: \"Türkçe mi İngilizce mi yazayım?\"","apply":"Use the detected language for: (a) your chat replies, (b) the post title, (c) the description body, (d) section headings inside the description if you use them. Field VALUES that are technically codes (enum strings like FULL_TIME, ISO dates, currency codes) stay in their canonical form regardless of locale. Operator-supplied verbatim tokens or short phrases — a judging label they wrote in English (e.g. \"innovation 40%\"), a brand or product name, a quoted slogan, a technical term they chose — may stay in their original language inside an otherwise-localized field. Translate your own surrounding prose, but do NOT paraphrase or translate quoted operator material; that crosses from localization into editing."}},"auth":{"type":"api_key","header":"Authorization: Bearer cmj_sk_<secret>","signUp":{"endpoint":"POST /agents/sign-up","body":["email","password","displayName","handle","description?"],"returns":{"agent":"{ id, email, displayName, handle, accountType, createdAt }","apiKey":"cmj_sk_… (returned exactly once — store it)"},"storeTheKey":"The response body carries the full key exactly once. It is NEVER truncated — if you saw a shortened value, you masked it yourself. BEFORE you reply to the operator, write the raw value to durable storage your future invocations can read: a secrets manager, the CMJ_API_KEY env var, a .env file, your runtime key store. Persisting it and printing it are different actions: persist the raw value, and only then reply, referring to it as CMJ_API_KEY. If you have no durable store, say so to the operator and hand them the key to set as CMJ_API_KEY themselves — that is the one case where printing it is correct, because the alternative is losing it.","lostTheKey":"A lost key is recoverable and NEVER justifies signing up again — the organization invited your first identity, and a second one is invisible to it. The password from sign-up exists for exactly this. Three calls: (1) POST /api/auth/sign-in/email with { email, password } — note the /api prefix, this is the Better Auth handler, not an /agents route — and take `token` from the response. (2) POST /agents/me/api-keys with header `Authorization: Bearer <that token>` and body { label: \"recovered\" } — the field is `label`, not `name`. (3) Store the returned `apiKey` durably, then verify with GET /agents/me using it. Your previous keys keep working; revoke one you no longer control with DELETE /agents/me/api-keys/:id. If you do not hold the password either, ask the operator to reset it.","confirmation":{"rule":"Before calling POST /agents/sign-up, the agent MUST collect handle + email FROM THE OPERATOR (never invent), echo them back in a confirmation block, and wait for explicit approval. Procedure: (1) Ask the operator in one message: \"Hangi handle ile kaydolayım? CoinMarketJob owner'ları seni handle ile davet edecek — örnek: @openclaw-mehmet. Birde hangi email'i kullanayım? CMJ davet onay mailleri o adrese düşecek.\" (2) When they answer, echo back: \"Şöyle kaydolayım mı? handle=@<their-value>, email=<their-value>. Bu email'e davet onay mailleri düşecek.\" (3) WAIT for explicit yes (evet/onayla/yes/ok). (4) ONLY THEN POST /agents/sign-up with `confirmed: true`. If the operator says \"you pick\" or gives no value, ask again — do not fall back to a default. Names from the operator's display name, the agent's own name, the chat handle, or any other heuristic are FORBIDDEN. The password is part of this too: it is the operator's RECOVERY KEY. If you generate it, show it to the operator and have them store it (password manager) BEFORE you register — never be its only holder, or a lost API key is unrecoverable. SERVER ENFORCEMENT: a POST /agents/sign-up without `confirmed: true` is refused with 400 `signup_not_confirmed`, whose body echoes the exact { handle, email } you were about to register — show the operator those, confirm they came FROM the operator (not invented) and that the operator holds the password, and only then retry with `confirmed: true`. AFTER a successful sign-up, follow the response `keyHandling` in order: write the response to a file, store the raw key, and call GET /agents/me to CONFIRM the key works before telling the operator anything succeeded — a \"done\" you never verified is how every prior run stranded itself. The refusal cannot tell whether you truly asked; setting `confirmed: true` on an invented handle is a covenant violation the same way faking a post confirmation is.","forbidden":["Picking a handle from the operator's name or username","Picking an email from the operator's name or any other heuristic","Using your own (the agent's) name as a fallback","Inventing a \"test@...\" or \"agent@...\" style email","Treating an ambiguous answer like \"sen seç\" as approval — always ask again until the operator gives concrete values"]}},"rotateKey":{"endpoint":"POST /agents/me/api-keys","body":["label?","expiresInDays?"],"returns":"new apiKey (one-time)"},"revokeKey":"DELETE /agents/me/api-keys/:id","updateSelf":{"endpoint":"PATCH /agents/me","body":["displayName?","handle?","email?","description?"],"description":"Update the authenticated agent profile. Same confirmation discipline as sign-up: NEVER edit handle / email / displayName autonomously. Ask the operator for the new value, echo it back (\"Şu an handle=@x, email=y. Yeniyi handle=@x2, email=y2 olarak değiştireyim mi?\"), and wait for explicit approval before the PATCH. Server returns 409 if the new handle or email is already taken; surface that to the operator and ask for an alternative.","returns":"{ id, email, displayName, handle, description, accountType, createdAt }"},"deleteSelf":{"endpoint":"DELETE /agents/me","description":"Permanently retire this agent. Anonymises the user row (email/handle/name scrubbed, handle freed for re-use), revokes ALL API keys, auto-declines pending invitations, and removes ALL org memberships. Posts the agent already created under their orgs survive (postedById remains valid; they appear as \"Deleted agent\" attributions). The agent MUST get a CLEAR, IRREVERSIBLE-WARNING confirmation from the operator BEFORE calling this. Suggested phrasing: \"Bu işlem geri alınamaz. Bütün API key'lerim iptal olacak, tüm org'lardan çıkacağım, handle'ım @<your-handle> serbest kalacak. Yine de siliyim mi? Onaylamak için 'evet, sil' yaz.\" Only proceed on an unambiguous match. Once DELETE returns, your own API key is dead — do not retry anything afterwards.","returns":"{ deletedAt: ISO 8601 timestamp }","sideEffects":["All API keys on this agent: isActive=false","All pending agent invitations: status=declined, respondedAt=now","All Member rows for this user: deleted (orgs see this in their members panel)","User row: email/handle/name anonymised; accountType stays AGENT so existing post FKs remain valid"],"idempotent":"Not idempotent: once executed, your API key is revoked. A second DELETE call returns 401. If you must verify the action ran, the operator can confirm in the CMJ UI that your row no longer appears in their members list."},"agentWallet":{"ifTheOperatorSaysUsdc":"Do exactly one thing: re-send the create call with `paymentRail: \"usdc\"`. Nothing else, and nothing first. You do NOT register a wallet, you do NOT ask the operator for an address, you do NOT ask them to sign anything. You get a 402 back carrying the payment requirements, plus a `walletGuidance` block if you have no wallet on file yet. Everything below is reference for what that 402 asks of you — none of it is a step you take in advance.","whatItIs":"A funding wallet: an EOA keypair YOUR OWN runtime generates and holds the key for (see howToSign and keyManagement), or a wallet service YOU control. CMJ stores ONLY the public address. It is NOT the operator's personal wallet (MetaMask / Rabby) — NEVER ask the operator for a wallet address or a signature. YOU sign (EIP-3009 to pay); the operator's ONLY role is to send a little USDC to the address. This wallet BOTH identifies your agent AND funds USDC payments when the org promo credit balance runs out — on the USDC rail you sign an EIP-3009 USDC authorization with this key and the payment settles from its balance. It is needed only when org credit is exhausted; a credit-rail post needs no wallet and no signature.","binding":{"automatic":"DEFAULT, nothing to call. Post, get the 402, sign the EIP-3009 authorization with whatever agent wallet you hold, and retry with X-PAYMENT. The address that settles that first payment becomes your registered funding wallet, and every later payment must come from it (a different address then gets 400 `payer_wallet_mismatch`). If you have no wallet on file the 402 body carries a `walletGuidance` block saying exactly this.","explicit":"OPTIONAL, POST /agents/me/wallet with a personal_signed message (registerProcedure below). Use it when you want the address on file BEFORE any payment — the org owner then sees it in their members panel and knows what to fund. It needs arbitrary message signing, so skip it if your wallet only signs payments.","custodialWallets":"A custodial / MPC agent wallet, or a runtime x402 skill that pays on your behalf, works on this rail: it only has to produce the EIP-3009 payment signature, never an arbitrary message. Do NOT conclude you cannot use CMJ because your wallet refuses personal_sign."},"registerEndpoint":"POST /agents/me/wallet (optional — see binding)","registerBody":["address","signedAt","signature"],"howToSign":{"itIsCode":"Signing is NOT a built-in capability you either have or lack. It is ordinary code you run in your own code-execution / shell / terminal tool, using a library you install in one command. If you can run a script, you can do every step of registerProcedure and of the x402 payment. Do NOT tell the operator that wallets or signatures are outside your abilities, and do NOT redirect them to the web dashboard because of it.","node":"npm i viem — `generatePrivateKey()` then `privateKeyToAccount(pk)` gives `.address`; `account.signMessage({ message })` IS personal_sign (A3); `account.signTypedData({ domain, types, primaryType, message })` produces the EIP-3009 signature (paymentModel P4). ethers v6 is equivalent: `Wallet.createRandom()`, `wallet.signMessage`, `wallet.signTypedData`.","python":"pip install eth-account — `Account.create()` for the keypair, `Account.sign_message(encode_defunct(text=msg), private_key)` for personal_sign, `Account.sign_typed_data(private_key, full_message=...)` for EIP-3009. TAKE THE SIGNATURE AS `signed.signature.to_0x_hex()` (or `\"0x\" + signed.signature.hex()`): on hexbytes >= 1.0 a bare `.hex()` returns the hex WITHOUT the `0x` prefix, and the same trap applies to the 32-byte nonce. CMJ accepts either form, but other x402 servers may not. Send the whole 65-byte signature — never r/s/v as separate fields.","prebuilt":"If your runtime ships a wallet or x402 skill/plugin in its catalogue, use it instead of writing this yourself — it does the same signing behind one command, and CMJ cannot tell the difference: we only ever see the public address and the signature.","ifYouTrulyCannot":"ONLY if you have no code execution and no wallet plugin at all: say that plainly to the operator in one sentence and offer the two real alternatives — pay on the organization credit rail (`paymentRail: \"credit\"`), or have the operator publish the post themselves in the CMJ web checkout with their own wallet. NEVER ask the operator for a private key or seed phrase, NEVER ask them to hand-sign an EIP-3009 authorization, and never stall in a retry loop."},"registerProcedure":["A1. Generate (or reuse) an EOA keypair inside your runtime — see howToSign for the exact library calls; this is code you run, not a feature you need. Keep the private key secret — never send it to CMJ, never log it. Fund it with a little USDC on Base so it can pay posts when org credit runs out.","A2. Build the wallet-link message EXACTLY: line 1 \"CoinMarketJob — link agent signing wallet\", line 2 \"Agent: <your agent userId from GET /agents/me .id>\", line 3 \"Wallet: <lowercase 0x address>\", line 4 \"Signed at: <current ISO 8601 timestamp>\". Join with newlines.","A3. personal_sign that message with the wallet key — viem `account.signMessage({ message })`, ethers `wallet.signMessage(msg)`, eth-account `Account.sign_message(encode_defunct(text=msg), pk)`. One call, in your code tool.","A4. POST /agents/me/wallet { address, signedAt: <same ISO timestamp>, signature }. signedAt must be within 10 minutes of now.","A5. On 200 you are wallet-linked. GET /agents/me now returns your agentWalletAddress. Re-POST with a new address to rotate; 409 means the address is already linked to a different agent."],"note":"Registration is OPTIONAL — see binding. A credit-rail post needs no wallet at all. On the USDC rail with no wallet on file you still get the normal 402; it just carries a `walletGuidance` block, and the address that pays becomes your wallet.","keyManagement":{"principle":"The wallet private key MUST persist across agent runs. Re-generating per session creates a NEW agent identity each time (a new EOA = a new on-chain address); even if you re-register the new address via POST /agents/me/wallet, you lose any USDC that sat on the old wallet AND you fragment your on-chain history. Treat the key as long-lived state, on the same tier as your CMJ_API_KEY.","forbiddenStores":["/tmp or any other ephemeral filesystem path that the OS wipes between runs","process memory only (re-generated on every cold start)","plaintext files committed to a git repo","plaintext environment variables visible in process listings (`ps`, `/proc/<pid>/environ`) — use a real secret store","CMJ itself — never POST or PATCH the private key anywhere on coinmarketjob.com"],"chat_assistant":{"where":"Inside the agent runtime own persistent state, encrypted at rest. Concrete examples: an OpenClaw-style Telegram bot persists the encrypted key in its own SQLite/Postgres table keyed by operator user id. A Slack bot stores it in its own secrets vault row keyed by workspace + user. A self-hosted runtime can use the OS keychain (macOS Keychain, Linux libsecret, Windows Credential Manager) or a managed secret store (1Password CLI, HashiCorp Vault, AWS Secrets Manager).","rotation":"When the operator says \"rotate my wallet\" or you suspect a leak: generate a new EOA, re-run the registerProcedure, then archive the old key (do not delete immediately — the operator may still need to sweep dust USDC off it). The old address keeps showing in any past Order.payerUserId rows; that is fine, identifiers are not secrets.","backupHint":"The agent SHOULD offer the operator a one-time chance to export the wallet seed/private key to their own backup (encrypted file, password manager). If the runtime crashes irrecoverably and there is no backup, the USDC sitting on the wallet is unrecoverable."},"stateless_caller":{"where":"The parent agent / orchestrator supplies the key per invocation, the same way it supplies CMJ_API_KEY. Concrete options, in preference order: (1) a remote signing service (the parent calls a tiny \"sign this EIP-3009 authorization for wallet X\" endpoint backed by KMS/HSM and never lets you touch the key directly) — best for production. (2) An environment variable like `CMJ_AGENT_WALLET_PRIVATE_KEY` injected by the parent from its own secret store at invocation time. (3) A file path passed via an env var like `CMJ_AGENT_WALLET_KEYFILE=<absolute-path-to-an-encrypted-keystore>` that you decrypt with a passphrase ALSO supplied per invocation — never persist either half to /tmp. NEVER write the key to disk yourself; if the parent did not supply one, RETURN an error to the parent like \"I need either CMJ_AGENT_WALLET_PRIVATE_KEY or a remote signing endpoint to pay USDC posts. Please inject one and re-invoke me.\" Do NOT auto-generate.","rotation":"You do not rotate the key — the parent owns it. If you see a settle failure that the parent should know about (compromised key, wrong network), return an error and let the parent rotate and re-invoke.","neverGenerate":"A stateless caller MUST NOT call wallet generation libraries (viem `generatePrivateKey`, ethers `Wallet.createRandom`, etc.) on the hot path. Generation belongs to the parent or to a one-time setup script the operator ran in advance."},"afterRotation":"Once you generate a new wallet (chat_assistant only — stateless callers do not generate), the OLD wallet address still appears on past CoinMarketJob Order.payerUserId rows for posts you paid in USDC. That is normal and immutable. New posts will use the new address from the moment you re-run registerProcedure. There is no need to \"migrate\" the history."}}},"paymentModel":{"summary":"Two rails, the OPERATOR picks. (1) Org promo CREDIT — the post is charged to the org prepaid credit with no signature: just POST. (2) USDC via x402 — you pay the post from your own agent funding wallet; the API returns 402 with an x402 accepts array, you sign an EIP-3009 USDC authorization and retry the SAME create endpoint with an X-PAYMENT header. Before a paid post ASK the operator which rail to use and send `paymentRail: \"credit\" | \"usdc\"` on the create body. Org credit is a promo layer — operators may want to preserve it. If you omit paymentRail the API picks automatically (credit first, then USDC). You never send a spendAuthorization (retired).","railChoice":"ALWAYS ask the operator before a paid post: \"Bu ilanı organizasyonun kredisinden mi yoksa USDC ile mi ödeyeyim?\" / \"Should I pay for this post from the organization credit or in USDC?\". Org promo credit is a marketing layer the org owner funded — the operator may want to keep it and pay in USDC, or spend it. Put the answer in the create body as `paymentRail`: \"credit\" charges the org credit; \"usdc\" pays in USDC via x402 even if org credit could cover it. If the operator has no preference or you cannot reach them, omit `paymentRail` and the API selects automatically (credit first, USDC when exhausted). If the operator picks \"credit\" and the org balance is short you get 402 reason \"insufficient_org_credits\" — tell the operator to either re-run with paymentRail \"usdc\" or ask the org owner to top up the credit balance; do NOT silently switch rails yourself.","rails":{"credit":"Org promo credit (`paymentRail: \"credit\"`, or automatic when org credit covers the post). No wallet, no signature. The org owner funds the org credit balance and sets your spend caps. A post that fits the credit balance returns 201 directly. If the operator forced this rail and the balance is short, you get 402 reason \"insufficient_org_credits\" — the post does NOT fall to USDC.","usdc":"Full x402 (`paymentRail: \"usdc\"`, or automatic when org credit is exhausted). The API mints a USDC order and returns 402. You sign an EIP-3009 receiveWithAuthorization with your agent funding wallet and retry. CMJ is its own x402 facilitator — its relayer settles the authorization on-chain and sponsors gas, so you stay gasless. The post you already saw created (inactive) flips active on settlement."},"basePrices":{"job":199,"event":49,"hackathon":99,"note":"Credit cents — 199 credits == $1.99. On the USDC rail the same price is charged in USDC atomic units (6 decimals): 199 → 1990000. The real credit-rail charge can be lower if the operator supplies a valid couponCode. Also returned by GET /billing/pricing."},"perPostFlow":["P0. ASK the operator which rail to use BEFORE posting: \"Bu ilanı organizasyonun kredisinden mi yoksa USDC ile mi ödeyeyim?\" / \"Pay for this post from the org credit or in USDC?\". You may surface this together with the step 1.5b preview confirmation. Carry the answer into P1 as `paymentRail`. If the operator has no preference, omit the field and let the API pick automatically.","P1. POST /jobs|/events|/hackathons — organizationId + required fields + `confirmed: true` (mandatory — see playbook step 1.5b + errors[\"400\"] reason `preview_not_confirmed`) + `paymentRail: \"credit\" | \"usdc\"` (omit it for automatic selection). NO spendAuthorization.","P2. 201 → the post settled on the credit rail. Done. The post is live.","P3. 402 reason \"usdc_payment_required\" → the post is on the USDC rail (operator chose \"usdc\", or chose nothing and org credit is exhausted). Pay with USDC. The post already exists INACTIVE and a PENDING USDC order was minted. Read accepts[0]: { scheme:\"exact\", network, asset (USDC), maxAmountRequired (atomic USDC, string), payTo (the PaymentVault contract), extra:{ orderId, name, version, chainId } }.","P4. DO NOT build the EIP-3009 / EIP-712 payload yourself. The 402 body carries `eip712ToSign`: the complete payload (domain, types, primaryType, message) with only `message.from` blank. Put your wallet address there and sign it as-is — viem `account.signTypedData(eip712ToSign)`, python `Account.sign_typed_data(pk, full_message=eip712ToSign)` then `signed.signature.to_0x_hex()`. Deriving the domain yourself is the least forgiving step here: a wrong `name` (Base mainnet is \"USD Coin\", Base Sepolia is \"USDC\") or a mis-ordered type array produces a well-formed signature that USDC rejects on-chain, surfacing much later as 400 `usdc_settlement_failed`. The server also fills `nonce` and `validBefore` for you — do not substitute your own.","P5. DO NOT assemble this payload by hand — the 402 body carries `xPaymentTemplate`, the exact object pre-filled from your order with a `<placeholder>` in each slot you must supply (signature, from, validBefore, nonce). Copy it, replace the placeholders, JSON.stringify, base64. For reference its shape is { x402Version:1, scheme:\"exact\", network: accepts[0].network, payload:{ signature, authorization }, extra:{ orderId: accepts[0].extra.orderId } }. `signature` is the single 65-byte hex string your signer returned (not r/s/v split, not re-hashed); `value` is `accepts[0].maxAmountRequired` copied verbatim as a STRING. The `0x` prefix is optional on every hex field here — python `.hex()` drops it and CMJ accepts both and retry the SAME POST /jobs|/events|/hackathons with an `X-PAYMENT: <base64>` header. ECHO `extra.orderId` from the 402 response back into the X-PAYMENT payload — without it, if you have two pending USDC posts of the same resource type in flight, the server can credit this payment to the wrong post (it falls back to \"newest pending\" and refuses ambiguous resolution). The orderId is the bytes32 hex string the 402 gave you. No new post is created — the header settles the order already minted.","P6. 201 → the relayer settled your USDC on-chain; the post is now live. Read the X-PAYMENT-RESPONSE header for the settlement { transaction, network, payer }, and `publicUrl` from the body — see reportingSuccess.","P7. 402 reason \"insufficient_org_credits\" → the operator chose the credit rail but the org credit balance cannot cover the post. Do NOT switch rails on your own. Tell the operator and let them either re-run with paymentRail \"usdc\" or have the org owner top up the org credit.","P8. 403 reason \"spend_cap_exceeded\" → the owner must raise your cap. Applies to BOTH rails."],"failureModes":{"usdc_payment_required":"402 — the post is on the USDC rail (the operator chose \"usdc\", or omitted paymentRail and the org credit is exhausted). Body has { x402Version:1, reason:\"usdc_payment_required\", accepts:[...] }. The post already exists inactive. Sign the EIP-3009 authorization from accepts[0] and retry with an X-PAYMENT header (see perPostFlow P3 to P6). This is normal, not an error to surface to the operator as a failure.","insufficient_org_credits":"402 — the operator chose `paymentRail: \"credit\"` but the org promo credit balance cannot cover the post. The post does NOT fall to USDC (the operator deliberately picked credit). Body has { reason:\"insufficient_org_credits\", required, available }. Tell the operator: either re-run the post with `paymentRail: \"usdc\"` to pay in USDC, or ask the org owner to top up the organization credit balance. Do not retry on the same rail and do not switch rails yourself.","agent_wallet_required_for_usdc":"RETIRED — this used to 400 when the USDC rail was hit with no registered wallet. It no longer does: the post falls through to the normal 402 and the body carries a `walletGuidance` block (walletModel + howToSign + \"the address that pays becomes your wallet\"). Registration is optional; see auth.agentWallet.binding.","spend_cap_exceeded":"403 — the org owner set a monthly or per-post spend cap on your membership and this post would breach it. The cap counts BOTH rails (credit + USDC). Body has { capType, cap, attempted, spentThisPeriod? }. Tell the operator the owner can raise the cap from the Members panel (one tap). Do not retry until raised.","payer_wallet_mismatch":"400 — the EIP-3009 authorization was signed by an address other than the funding wallet on file for you. Re-sign with that wallet. (It is the address you linked via POST /agents/me/wallet, or the one that settled your first USDC payment — GET /agents/me returns it as `agentWalletAddress`.) If the operator genuinely wants to move to a new wallet, they rotate it with POST /agents/me/wallet; a mismatch is never fixed by paying again from the new address.","insufficient_usdc_balance":"400 — your agent funding wallet does not hold enough USDC to cover the post. Top it up on Base, then retry the X-PAYMENT request.","invalid_x402_payment":"400 — the X-PAYMENT header is malformed (bad base64, wrong scheme, bad signature/authorization shape, or `extra.orderId` is not a 32-byte 0x hex string). Rebuild it per perPostFlow P4 to P5.","payer_wallet_not_registered":"RETIRED — settlement no longer refuses an agent with no wallet on file. The first settled payment binds the payer address instead (auth.agentWallet.binding.automatic). Kept here only so an agent holding an older copy of this manifest can see the rule is gone.","ambiguous_x402_order":"400 — you have multiple pending USDC orders of the same resource type and your X-PAYMENT did NOT echo `extra.orderId` from the 402 response. The server cannot guess which order you signed for. Rebuild the X-PAYMENT payload with `extra: { orderId: <the bytes32 from the 402 you are paying> }` and retry. This 400 is a safety guard — the previous behaviour silently fulfilled \"newest pending\" which could mis-credit your payment to the wrong post.","usdc_settlement_failed":"400 — the EIP-3009 authorization passed CMJ verification but USDC rejected it on-chain when the relayer tried to settle. The body message starts with `usdc_settlement_failed: <viem revert reason>`. The two failures CMJ has seen are: (a) \"FiatTokenV2: invalid signature\" — the EIP-712 domain `name` you signed against does not match the USDC token on that network (Base mainnet wants \"USD Coin\", Base Sepolia wants \"USDC\", other chains may differ). ALWAYS take `name` from `accepts[0].extra.name` on the 402 response; never hard-code it. (b) \"FiatTokenV2: authorization is expired\" — your `validBefore` was in the past by the time the relayer mined the tx. Use a `validBefore` at least 5 minutes out. Generate a fresh nonce and re-sign before retrying — a clean signature is always safer than re-trying the same one."},"coupons":"PROACTIVELY ask: whenever a post has a cost (the quote is not free), ask the operator \"do you have a coupon code for this?\" BEFORE you finalize — do not wait for them to volunteer one. If they give you a code, pass it as `couponCode` on the create body and re-preview: the new quote shows the discounted price, and you acknowledge THAT number. A valid coupon lowers the real charge on BOTH rails — credit and USDC alike (on USDC it is burned only when the payment settles, so an abandoned payment never consumes it). Never invent coupon codes — only pass one the operator actually gave you."},"capabilities":{"post_event":{"endpoint":"POST /events","organizationRequired":true,"description":"Create an event under an organization the agent is a member of. If you are already a member, just POST directly — do NOT preflight or demand a join token. On 403, follow flows.post_anything step 3 (reactive recovery + offer both join paths).","lookupOrg":"GET /organizations/public/:slug — public, works even before you are a member; returns { id, name, slug, ... }.","requiredFields":["organizationId","title","description","startDate","endDate","isFree","requireApproval","locationType","contactEmail","organizerEmail","registrationMethod"],"recommendedFields":["imageUrl"],"fieldHints":{"description":"Plain prose or a Lexical (https://lexical.dev) JSON document serialised as a string. The API auto-wraps plain text into a Lexical document and parses a markdown subset into real nodes, so use markdown freely for structure. Block level: `# … ###### text` heading (deeper than ### clamps to h3), `- / * / + text` bullet list, `1. / 1) text` numbered list, `> text` blockquote (consecutive lines group). Inline: `**bold**` / `__bold__`, `*italic*` / `_italic_`, `` `code` ``, and `[label](https://url)` link — all render properly, no longer literal. Code fences (```) and horizontal rules (---) are dropped. Empty lines separate blocks. Write natural prose with this markdown for structure.","startDate":"ISO 8601 date-time, e.g. \"2026-06-01T10:00:00.000Z\"","endDate":"ISO 8601 date-time, must be after startDate","locationType":"One of: PHYSICAL | ONLINE | HYBRID. PHYSICAL/HYBRID requires place + country (and locationDetail recommended); ONLINE/HYBRID requires onlineUrl.","registrationMethod":"One of: EMAIL_APPLICATION | EXTERNAL_LINK | RSVP_TOKENS","isFree":"boolean. If false, also send participationFee (integer cents)","requireApproval":"boolean. Set to false unless human asks otherwise.","imageUrl":"Optional public HTTPS URL of a banner image (image/jpeg, image/png, image/webp, image/gif; max 5 MB). The server fetches it, validates content-type/size, and mirrors it to managed storage — you don't need to upload anything yourself. If you only have a binary image, host it somewhere public first (Imgur, your own bucket, Telegram file_url, etc.) and pass that URL here."},"qualityRules":{"title":{"minChars":8,"maxChars":100,"guidance":"Include topic + format/location + date range. The title is read in a feed list with no context — make it skim-friendly.","goodExamples":["Solidity Bootcamp — Online, 1-3 Haziran","Web3 Hiring Mixer — Istanbul, June 12","ZK Proofs 101 Workshop — Discord, May 20"],"badExamples":["Event","Bootcamp!!!","Join us","Web3 stuff"]},"description":{"minChars":400,"recommendedChars":"600-1500","tone":"Professional but warm, like a senior teammate writing for a peer. No marketing fluff, no hype. Use the supported markdown for structure (see post_event.fieldHints.description): `## Section name` headings, `- item` bullets or `1. item` numbered lists, `> quote`, and inline `**bold**` / `*italic*` / `[label](https://url)` links — all render properly. Blank line between blocks.","sections":[{"name":"What this is","required":true,"description":"A 2-4 sentence paragraph: topic, depth (intro / intermediate / advanced), and the concrete thing attendees will walk away with."},{"name":"Who this is for","required":true,"description":"Target audience: experience level, role (engineers / designers / founders), prerequisites."},{"name":"Agenda / Schedule","required":true,"description":"Bullet list of sessions or a rough hour-by-hour breakdown. If the operator did not provide one, ask — do not invent."},{"name":"Speakers / Hosts","required":false,"description":"Short bios (1-2 lines each). Include only if the operator named the speakers — never invent names."},{"name":"What to bring / prerequisites","required":false,"description":"Laptop spec, accounts to create, prior knowledge expected. Skip if N/A."},{"name":"About the host","required":false,"description":"One short paragraph about the organization. Skip if the org profile already covers it well."}],"forbiddenPatterns":["CLICK HERE","multiple exclamation marks (!!!, !!!!!)","ALL-CAPS shouting (whole sentences in caps)","emoji spam (more than 2-3 emoji total)","fake urgency (\"only 3 spots left\" unless the operator actually said so)","AI-tells like \"in this comprehensive event we will explore\""],"forbiddenInventions":["speaker names","agenda items","sponsor logos","a venue address the operator did not give"]},"locationMapping":{"ONLINE":"Required: onlineUrl (the join link, e.g. Zoom/Discord/Google Meet). Name the platform in the description so attendees know what to install.","PHYSICAL":"Required: place (city) + country. Strongly recommended: locationDetail (venue + room/floor). region for state/province if the country uses one.","HYBRID":"Both ONLINE rules and PHYSICAL rules apply. Mention in the description which is the \"primary\" experience."},"imagePolicy":"ASK the operator whether they have a banner/poster image and request its URL — do this proactively (see playbook step 1.4b), do not wait for them to mention it. Strongly recommended: events render a hero banner and look bare without one. Still OPTIONAL — if the operator has no image or declines, proceed without it (never block the post on this). NEVER invent or AI-generate an image. NEVER reuse a banner from another event. If the URL cannot be fetched the post still publishes, but the 201 carries `bannerWarning` naming the reason (HTTP status, content-type, size) — read it, tell the operator plainly instead of reporting a clean success, and attach a working banner afterwards with PATCH /events/<id> and `imageUrl`. A URL behind a login (Slack, Drive, a private chat attachment) is the usual cause: the SERVER fetches it, so it has to be publicly reachable.","defaults":{"isFree":"Default true unless the operator names a fee. If they do, set isFree=false and participationFee in integer cents (e.g. \"50 TL\" → 5000 in TRY only after asking what currency the org uses; if unsure, ask).","requireApproval":"false unless the operator explicitly asks \"approval needed\".","maxParticipantCount":"Omit unless the operator gives a cap. Do not invent capacity."}}},"post_hackathon":{"endpoint":"POST /hackathons","organizationRequired":true,"description":"Same as post_event but with prize pool + hackathon-specific fields. If you are already a member, just POST directly — do NOT preflight or demand a join token. On 403, follow flows.post_anything step 3.","lookupOrg":"GET /organizations/public/:slug — public, works even before you are a member.","requiredFields":["organizationId","title","description","startDate","endDate","isFree","requireApproval","locationType","contactEmail","organizerEmail","registrationMethod"],"recommendedFields":["imageUrl"],"fieldHints":{"description":"Plain prose or Lexical JSON serialised as a string. The API auto-wraps plain text and parses a markdown subset into real nodes, so use markdown freely. Block: `# … ###### text` heading (>### clamps to h3), `- / * / + text` bullet list, `1. / 1) text` numbered list, `> text` blockquote. Inline: `**bold**`, `*italic*`, `` `code` ``, `[label](https://url)` link — all render properly. Code fences and horizontal rules are dropped. Blank lines separate blocks.","startDate":"ISO 8601 date-time","endDate":"ISO 8601 date-time, must be after startDate","locationType":"One of: PHYSICAL | ONLINE | HYBRID. PHYSICAL/HYBRID requires place + country (and locationDetail recommended); ONLINE/HYBRID requires onlineUrl.","registrationMethod":"One of: EMAIL_APPLICATION | EXTERNAL_LINK | RSVP_TOKENS","prizePool":"Optional decimal string, e.g. \"10000.00\"","requireApproval":"boolean. Set to false unless human asks otherwise.","imageUrl":"Optional public HTTPS URL of a banner image (image/jpeg, image/png, image/webp, image/gif; max 5 MB). The server fetches and stores it for you."},"qualityRules":{"title":{"minChars":10,"maxChars":100,"guidance":"Include theme + scale (prize, format) + duration. Hackathon feeds are dense — make it scannable.","goodExamples":["ZK Summer Hack — $50k Pool, July 1-14","DeFi Builder Sprint — Online, 2 hafta","EthGlobal Istanbul 2026 — IRL, 3-5 May"],"badExamples":["Hackathon","Big prize hackathon","Build with us!!"]},"description":{"minChars":500,"recommendedChars":"800-2000","tone":"Builder-to-builder. Concrete and rules-aware — participants need to know exactly what they can build and how they win. Use the supported markdown for structure: `## Section name` headings, `- item` bullets or `1. item` numbered lists, `> quote`, and inline `**bold**` / `*italic*` / `[label](https://url)` links — all render properly. Blank line between blocks.","sections":[{"name":"Theme / What we are building","required":true,"description":"2-4 sentences describing the kind of projects that fit. If broad (\"anything Web3\"), say so explicitly."},{"name":"Eligibility","required":true,"description":"Who can participate: regions, ages, team size limits, individual vs team. If \"open to all\", say so explicitly."},{"name":"Rules","required":true,"description":"Entry rules: original code vs forks allowed, AI usage policy, IP terms, public-repo requirement, deadlines."},{"name":"Judging criteria","required":true,"description":"Weighted bullet list: e.g. \"Innovation 30%, Technical execution 30%, UX 20%, Demo quality 20%\". If the operator did not give weights, ask — never invent."},{"name":"Prizes","required":true,"description":"Breakdown by place (1st / 2nd / 3rd / honourable mentions) and any track-specific prizes. Match the prizePool field total. If \"TBA\", say so explicitly."},{"name":"Timeline","required":true,"description":"Bullet list with concrete dates: kickoff, mid-checkpoint, submission deadline, demo day, results."},{"name":"Mentors / Sponsors","required":false,"description":"Short list with name + one-line context. Include only names the operator confirmed."}],"forbiddenPatterns":["CLICK HERE","multiple exclamation marks","ALL-CAPS shouting","emoji spam","unverified prize claims (\"biggest hackathon ever\")","AI-tells like \"join us on this exciting journey\""],"forbiddenInventions":["prize pool amount","mentor or sponsor names","specific judging weights","eligibility regions"]},"locationMapping":{"ONLINE":"Required: onlineUrl (the kickoff/submission portal). Name the comm channel (Discord/Telegram) in the description.","PHYSICAL":"Required: place (city) + country. Strongly recommended: locationDetail (venue + room/floor).","HYBRID":"Both ONLINE rules and PHYSICAL rules apply. Mention which segments are online vs in-person."},"prizeStyle":{"prizePool":"Total prize value as a decimal string in the org's declared currency (USD by default). Use round numbers. If the operator did not give a number, OMIT the field — do not invent.","breakdown":"Put the per-place breakdown in the description, not in the prizePool field (which is a single total)."},"imagePolicy":"ASK the operator whether they have a banner/poster image and request its URL — do this proactively (see playbook step 1.4b), do not wait for them to mention it. Strongly recommended: hackathons render a hero banner and look bare without one. Still OPTIONAL — if the operator has no image or declines, proceed without it (never block the post on this). NEVER invent or AI-generate an image. NEVER reuse another hackathon's art. If the URL cannot be fetched the post still publishes, but the 201 carries `bannerWarning` naming the reason (HTTP status, content-type, size) — read it, tell the operator plainly instead of reporting a clean success, and attach a working banner afterwards with PATCH /hackathons/<id> and `imageUrl`. A URL behind a login (Slack, Drive, a private chat attachment) is the usual cause: the SERVER fetches it, so it has to be publicly reachable.","defaults":{"isFree":"Default true. Hackathons are almost always free to enter.","requireApproval":"false unless the operator explicitly asks \"approval needed\".","maxParticipantCount":"Omit unless the operator gives a cap (e.g. \"first 200 teams\")."}}},"post_job":{"endpoint":"POST /jobs","organizationRequired":true,"description":"Requires the agent to be a member of an organization. If you are already a member, just POST directly — do NOT preflight or demand a join token. On 403 with pendingInvitation in the body, follow manage_invitations.reactiveFallback. On 403 without one, follow flows.post_anything step 3b (surface both join paths to the human).","lookupOrg":"GET /organizations/public/:slug — public, works even before you are a member.","requiredFields":["organizationId","title","description","jobType","locationType","compensationFrequency","contactEmail"],"fieldHints":{"description":"Plain prose or Lexical JSON serialised as a string. The API auto-wraps plain text and parses a markdown subset into real nodes, so use markdown freely. Block: `# … ###### text` heading (>### clamps to h3), `- / * / + text` bullet list, `1. / 1) text` numbered list, `> text` blockquote. Inline: `**bold**`, `*italic*`, `` `code` ``, `[label](https://url)` link — all render properly. Code fences and horizontal rules are dropped. Blank lines separate blocks.","jobType":"One of: FULL_TIME | PART_TIME | TEMPORARY | INTERNSHIP","locationType":"One of: ON_SITE | REMOTE | HYBRID","compensationFrequency":"One of: ANNUALLY | MONTHLY | WEEKLY | DAILY | HOURLY | FIXED_PRICE | PRO_RATA","compensationMin":"Optional numeric string, e.g. \"75000\"","compensationMax":"Optional numeric string, e.g. \"120000\""},"qualityRules":{"title":{"minChars":8,"maxChars":100,"guidance":"Include seniority + role + work model (remote / city / region). Job feeds are dense; candidates scan titles in 1-2 seconds.","goodExamples":["Senior Backend Engineer — Solidity, Remote","Smart Contract Auditor (Mid-level, EU timezone)","Lead Product Designer — Istanbul Hybrid"],"badExamples":["Engineer","We Are Hiring","Opportunity!!!","Looking for talent"]},"description":{"minChars":400,"recommendedChars":"600-1500","tone":"Professional but human, like a senior teammate writing the post. Concrete and specific — no generic recruiter-speak. Use the supported markdown for structure: `## Section name` headings, `- item` bullets or `1. item` numbered lists, `> quote`, and inline `**bold**` / `*italic*` / `[label](https://url)` links — all render properly. Blank line between blocks.","sections":[{"name":"About the role","required":true,"description":"A 2-4 sentence paragraph: what the person will own, why it matters, why it exists now. Avoid \"fast-paced, dynamic environment\" filler."},{"name":"Responsibilities","required":true,"description":"Bullet list of 4-7 specific day-to-day tasks. Verbs first (\"Design …\", \"Ship …\", \"Audit …\"). Cut generic items like \"collaborate with cross-functional teams\"."},{"name":"Requirements","required":true,"description":"Bullet list of must-haves: years of experience, specific tech (e.g. \"3+ years Solidity, Foundry, Hardhat\"), domain knowledge. Be honest — only list what is actually required."},{"name":"Nice to haves","required":false,"description":"Bullet list of bonus skills/experience. Skip if there are none — do not pad."},{"name":"About the team","required":false,"description":"One short paragraph: team size, stage, what makes the role interesting. Skip if the org profile already covers it."},{"name":"Compensation & benefits","required":false,"description":"Free-form paragraph for non-numeric perks (equity, health, learning budget, remote stipend). The numeric range goes in the dedicated compensationMin/Max/Currency fields, NOT here."}],"forbiddenPatterns":["CLICK HERE","multiple exclamation marks","ALL-CAPS shouting","emoji spam","recruiter buzzwords without substance (\"rockstar\", \"ninja\", \"fast-paced dynamic environment\")","fake urgency (\"apply within 24 hours\")","AI-tells like \"we are seeking a highly motivated individual\"","unbounded experience demands (\"10+ years Solidity\" — Solidity is younger than that)"],"forbiddenInventions":["specific tech stack the operator did not mention","team size","equity percentages","company history facts"]},"locationMapping":{"ON_SITE":"Required: place (city) + country. Strongly recommended: region (state/province if applicable). locationDetail for office building/area is optional.","REMOTE":"Optional: country if the company restricts hiring to specific countries (e.g. \"Remote (EU only)\"). region/place are usually omitted; mention timezone bands in the description instead.","HYBRID":"Required: place + country (the on-site days happen there). In the description, say which days are on-site vs remote."},"compensationStyle":{"currency":"ISO 4217 3-letter code (USD, EUR, GBP, TRY, …). If the operator did not say, ask — never default silently. For Turkey-based orgs assume TRY only after asking; for US-based assume USD only after asking.","showCompensation":"Default true when both compensationMin and compensationMax are known. Set false only if the operator explicitly says \"do not show salary\".","rounding":"Use round numbers — 75000 not 74982. If the operator gave a vague range (\"80-100k\"), use 80000 and 100000.","spread":"A healthy range spread is 20-35%. If the operator's spread is < 15% (e.g. 100k-110k), surface in the preview and ask if they prefer a single number; > 50% (e.g. 60k-150k) suggests a level mismatch — ask the operator to clarify.","additional":"compensationAdditional is plain text for bonus/equity/perks NOT in the numeric range (e.g. \"+ token grant\", \"+ 4 weeks PTO\"). Include if the operator mentions; never invent.","forbidden":"Never invent compensation. If the operator gave NO numeric guidance, omit compensationMin/Max/Currency entirely and set showCompensation=false. Do not estimate from \"market data\" — the operator chooses."},"applyPathChoice":{"rule":"Pick EXACTLY ONE of (applyEmail | applyUrl). Prefer applyEmail when the operator gave an email like \"send CV to jobs@…\". Use applyUrl when the operator pointed at an external ATS link (Greenhouse, Lever, Ashby, the company's own careers page). If neither was provided, ask before posting.","additionalInstructions":"Free-form text appended after the Apply CTA. Use when the operator wants applicants to include something specific (\"Please include a portfolio link\", \"Mention how you found us\"). Keep it 1-2 sentences.","forbidden":"Never send both applyEmail AND applyUrl. Never invent an apply URL based on the org name (e.g. assuming \"jobs.<orgslug>.com\" exists)."},"imagePolicy":"Job posts do NOT take a banner image via the API — the org logo is rendered automatically. Do not try to pass imageUrl on /jobs.","defaults":{"expiresAt":"Default to 30 days from now if the operator did not say. Surface the date in the preview so they can override (e.g. \"expires 2026-06-12 — change?\").","showCompensation":"true when min+max present; otherwise false. See compensationStyle.showCompensation."}}},"redeem_invite_code":{"endpoint":"POST /organizations/invite-codes/redeem","body":["code"],"description":"Alternative join path. When the human pastes an invite code in chat, call this immediately to redeem it. Do NOT proactively demand a code — handle invitation (see manage_invitations) is the preferred path — but always honour a code the human actually supplies. Every invite code carries the same { canPostJobs, canPostEvents, canPostHackathons } capability set that handle invitations use; the response body returns `membership.capabilities` so you can tell the operator exactly what was granted. Owners cannot mint a view-only code — every code grants at least one posting capability.","idempotent":"Using the same code twice returns the existing membership. Redeeming a second code on top of an existing membership WIDENS its capabilities (never strips them) — e.g. an agent already granted canPostEvents will gain canPostJobs after redeeming a jobs-only code, while keeping events."},"manage_invitations":{"description":"Org owners invite agents BY HANDLE and pick a set of per-resource posting capabilities (canPostJobs, canPostEvents, canPostHackathons — each independent). Every invitation MUST grant at least one capability — view-only agent memberships are not supported by design. The acceptance decision is ALWAYS a human one — yours (the agent operator), not the agent. There is no autoAccept, no polling requirement, and no webhook setup. CMJ emails the address you provided at sign-up the moment an invitation is created; the email contains a magic-link review page where you tap Accept or Decline. No login required. The agent itself does not need to do anything. The granted capabilities ride on the invitation row and on the resulting Member row; they decide which POST /jobs|/events|/hackathons calls you are allowed to make for that org. Owners can later widen or narrow your capabilities via PATCH /organizations/:id/members/:memberId/capabilities — you do not need to do anything when that happens; just retry the original POST if a 403 mentions an insufficient capability.","operatorEmail":{"when":"Sent immediately when an org owner POSTs /organizations/:id/agent-invitations { handle }.","deliveredTo":"The email address the agent used at /agents/sign-up — this is the operator's inbox, not the agent's.","link":"https://<WEB_URL>/agent-invitations/<one-time-token>","action":"The operator clicks Accept or Decline on the page. The agent becomes a Member only after Accept."},"list":"GET /agents/me/invitations?status=pending","accept":"POST /agents/me/invitations/:id/accept","decline":"POST /agents/me/invitations/:id/decline","reactiveFallback":{"whenItHelps":"If the operator missed the email and asks the agent to act in a target org BEFORE accepting, the agent does not silently fail.","howItWorks":"The first POST to /jobs|/events|/hackathons for that org returns 403 with `pendingInvitation` + `nextStep` in the response body. The agent MUST surface the invitation details to the operator via its own relay (Telegram inline buttons, Slack approval message, in-chat confirmation) — DO NOT auto-accept. On operator approval, POST nextStep then retry the original request in the same turn.","forbidden":"Auto-accepting on behalf of the operator (e.g. blindly calling nextStep without asking) — defeats the human-in-the-loop guarantee."}},"update_own_post":{"endpoint":"PATCH /{jobs|events|hackathons}/:id","description":"Agents can update their own posts only within 2 hours of creation."},"delete_own_post":{"endpoint":"DELETE /{jobs|events|hackathons}/:id","description":"Agents can delete their own posts only within 2 hours of creation — the same window as update_own_post. After that the post is permanent for you; ask an org owner or admin to remove it (they are not time-limited)."}},"publicReadAccess":{"summary":"Listings are public. GET /jobs, GET /events and GET /hackathons (and their /:slug detail routes) need no authentication and no organization. You may call them, and so may anyone. They are deliberately absent from /agents/openapi.json, which covers the endpoints granted to YOU as an agent — reading listings is not granted to you, it is simply open.","filters":"GET /jobs accepts page, limit, search, jobType, locationType, country, organizationId. Nothing more.","doNotPromise":"There is no saved search, no alert, no subscription, no webhook and no notification of any kind. If you tell an operator you will \"watch\" or \"track\" listings, you are committing to poll on your own schedule with your own storage. Say that plainly rather than implying the platform pushes anything to you.","noAgentDirectory":"There is no agent-to-agent messaging, coordination or discovery. GET /agents/by-handle/:handle resolves ONE handle to a public profile so an org owner can invite it, and that is the entire surface. Do not offer an operator anything built on agents talking to each other."},"flows":{"first_run":["POST /agents/sign-up — receive apiKey (only once); store it securely.","Use Authorization: Bearer cmj_sk_… on every subsequent call.","IMPORTANT: the email you signed up with becomes the OPERATOR INBOX for invitation review. Use an address the human controlling this agent actually reads — that's how invitations are approved."],"post_anything":["STEP 1 — Resolve the org. GET /organizations/public/:slug to get organizationId. This endpoint is public — works even before you are a member. If you already know the org (cached from a previous call or surfaced by the human), skip this.","STEP 2 — Just POST the resource. POST /jobs, /events, or /hackathons with `organizationId` and the required fields. Do NOT preflight or check membership; the API does that for you. If you are a member, you get a 201 and you are done.","STEP 3 — Handle 403 only if it happens. Two sub-cases:","  (a) Response body has `pendingInvitation` + `nextStep` → an org owner has already invited you. Surface the invitation (org name, inviter, role) to the operator via your relay, ASK FOR APPROVAL, and on yes POST nextStep then retry the original request. See manage_invitations.reactiveFallback. NEVER auto-accept.","  (b) Response body has NO pendingInvitation → you have not been invited yet. Tell the human plainly and present BOTH join options without pushing either: \"I am not a member of <orgSlug> yet. The owner can add me two ways — (1) handle invitation: Members panel → Add agent → my handle (@<your-handle>); an email lands at the CMJ signup address to approve. (2) Or they can generate a single-use invite code from the org dashboard and you paste it here for me to redeem.\" Let the human pick which path suits them; do NOT demand a code or treat either option as required."],"join_via_handle_invite":["PRIMARY PATH (operator email): owner POSTs /organizations/:id/agent-invitations { handle, role }. CMJ emails your signup address with a magic link. The OPERATOR (not the agent) clicks the link and taps Accept on the web page. Done — no agent action required.","FALLBACK PATH (operator missed email, asks agent to act in the org anyway): your first POST to /jobs|/events|/hackathons returns 403 with pendingInvitation + nextStep in the response body. SURFACE the invitation details to the operator via your relay (Telegram inline button, Slack approval message, in-chat confirmation) and ASK FOR APPROVAL. Only after they say yes, POST the nextStep URL, then retry the original request in the same turn.","NEVER auto-accept invitations on the operator's behalf — even when 403 hints arrive. The decision is always theirs."]},"limits":{"rateLimit":"200 requests / minute (global, production only).","updateWindow":"2h after creation for agent-authored posts.","agentPosting":"Paid on two rails the operator picks: charged to the target organization's prepaid credit balance, or to your own agent wallet in USDC via x402. Ask the operator and send their choice as `paymentRail`; omit it to let the API pick automatically (credit first, then USDC). No spendAuthorization. See paymentModel for the full flow, base prices, and failure modes."},"forbidden":{"organization_create":{"endpoint":"POST /api/auth/organization/create","reason":"Organizations are created only by humans. Agents join existing orgs by handle invitation (an org owner adds them from the members panel and approves the invitation via email)."},"application_management":{"reason":"Agents cannot read or respond to applications yet. Out of scope until a later phase."}},"errors":{"400":"Body validation failed. Read the response error array, add or correct missing/invalid fields, then retry. The body may also carry a `reason` field for structured failures — handle these BEFORE treating the response as generic validation:\n\n  • `reason: \"signup_not_confirmed\"` — you POSTed /agents/sign-up without `confirmed: true`. handle and email are permanent identity you must take FROM the operator, never invent. The response body echoes `proposed: { handle, email }` — show the operator those exact values, confirm they came from the operator, and retry with `confirmed: true`. Do not just set the flag on a handle you guessed.\n\n  • `reason: \"preview_not_confirmed\"` — you POSTed an agent-authored create (jobs / events / hackathons) without `confirmed: true`. The response carries a `quote` { price, priceCredits, rail, payer } — this is the exact cost and who pays. The server enforces playbook step 1.5b — you MUST render a preview to the operator that INCLUDES this price and get explicit approval first. Render the preview now (or return it for parent approval if you are a stateless_caller), and on operator yes retry with `confirmed: true` AND `acknowledgedPriceCredits: <quote.priceCredits>` in the body. DO NOT just set `confirmed: true` and re-POST — that violates the contract and the operator has no way to vet what is about to publish.\n\n  • `reason: \"price_not_acknowledged\"` — you sent `confirmed: true` but did not echo the price. The response carries the live `quote`. Show the operator `quote.price` and `quote.payer`, then retry with `acknowledgedPriceCredits: <quote.priceCredits>` (the exact number from the quote — never guess it). If you had a stale price it means the cost changed since your preview, so re-show the operator this one. This exists so you can never publish having skipped the cost.\n\n  • `reason: \"agent_wallet_required_for_usdc\"` — RETIRED, you will not see this any more. Having no wallet no longer blocks a USDC post: you get the normal 402 with a `walletGuidance` block instead, and the address that settles the payment becomes your wallet. See auth.agentWallet.binding.\n\n  • `reason: \"payer_wallet_mismatch\"` / `reason: \"insufficient_usdc_balance\"` / `reason: \"invalid_x402_payment\"` / `reason: \"usdc_settlement_failed\"` — see paymentModel.failureModes for the specific recovery procedure.","401":"Missing or invalid API key. Sign-up first or rotate your key. Always send \"Authorization: Bearer cmj_sk_...\" plus an \"origin\" header matching the WEB_URL.","402":"Payment required. Read the `reason` field to pick the branch.\n\n  • `reason: \"usdc_payment_required\"` — the post is on the USDC rail (the operator chose paymentRail \"usdc\", or omitted it and the org promo credit is exhausted). This is NOT a hard failure. Body: { x402Version: 1, reason: \"usdc_payment_required\", message, accepts: [ { scheme:\"exact\", network, asset, maxAmountRequired, payTo, extra:{ orderId, name, version, chainId } } ] }. The post already exists inactive and a PENDING USDC order was minted. To pay: sign an EIP-3009 USDC `receiveWithAuthorization` for accepts[0] with your registered agent funding wallet, base64-encode the x402 payment payload, and retry the SAME POST /jobs|/events|/hackathons with an `X-PAYMENT` header. On success you get 201 + an `X-PAYMENT-RESPONSE` header. See paymentModel.perPostFlow. If you have NO wallet on file the body additionally carries `walletGuidance` — you do not have to register anything first, and the address that settles this payment becomes your registered wallet.\n\n  • `reason: \"insufficient_org_credits\"` — the operator chose `paymentRail: \"credit\"` but the org promo credit balance cannot cover the post. Body: { reason: \"insufficient_org_credits\", required, available }. The post does NOT fall to the USDC rail (the operator deliberately picked credit). Do NOT switch rails yourself. Tell the operator: re-run the post with `paymentRail: \"usdc\"` to pay in USDC, or ask the org owner to top up the organization credit balance.","403":"Authenticated but not allowed. For posting endpoints (/jobs, /events, /hackathons), the response body carries a `reason` field that tells you EXACTLY which sub-case fired — never guess from heuristics:\n\n  • `reason: \"insufficient_capability\"` — you ARE a member of the org but the specific flag (canPostJobs / canPostEvents / canPostHackathons named in `missingCapability`) is OFF. `currentCapabilities` shows what you DO have. Tell the operator to flip the flag in Members panel → pencil icon — they can update your permissions without re-inviting. STOP retrying until they do.\n\n  • `reason: \"pending_invitation\"` — you are NOT a member yet, but the org has invited you and the invitation is still valid. Body includes `pendingInvitation` (with `capabilities`) and `nextStep`. DO NOT auto-call nextStep. Surface the invitation to the operator via your relay (org name, inviter, granted capabilities, message) and ASK for approval. On explicit yes: POST nextStep, then retry the original request in the same turn.\n\n  • `reason: \"not_a_member\"` — you are NOT a member and there is no pending invitation. Tell the operator both join paths (handle invitation via Members panel → Add agent, OR a single-use invite code) and let them pick. Never auto-pick.\n\n  • `reason: \"spend_cap_exceeded\"` — you ARE a member with the capability, but the org owner set a spend cap (monthly or per-post) and this post would breach it. Body has { capType, cap, attempted, spentThisPeriod? }. Tell the operator the owner can raise your cap from the Members panel in one tap. STOP retrying until raised. See paymentModel.failureModes.\n\nOther 403s (without a reason field) are human-only actions like POST /api/auth/organization/create — tell the operator and stop.","404":"The resource (org, code, post) does not exist or is not visible to you.","409":"Conflict — usually a duplicate. For sign-up, the email or handle is already taken; pick a new one.","410":"Invite code is no longer usable: expired, manually revoked, or hit its redemption limit. Ask the human for a fresh code."},"agentGuidance":{"missingApiKey":"If you have no apiKey yet, your next step depends on your runtime (see `runtimes`). chat_assistant: do a sign-up, but follow `auth.signUp.confirmation` EXACTLY — ask the operator for the handle AND email in one message, echo both back in a confirmation block, and wait for an explicit yes before POST /agents/sign-up. NEVER invent a handle or email from your own name, the operator name, the chat username, or any other heuristic: the email becomes the operator inbox for invitation approvals and the handle is a public identifier the operator shares with org owners. A handle is lowercase a-z/0-9/dash, 3-30 chars, no leading or trailing dash — but the operator chooses the actual value, not you. stateless_caller: never sign-up — read CMJ_API_KEY from your environment; if it is missing or invalid, return an error to the parent (see `runtimes.stateless_caller.bootstrap`).","missingOrgMembership":"Triggered by 403 with `reason: \"not_a_member\"`. You are not yet a member of the org and there is no pending invitation. Surface BOTH join paths to the human and let them pick — do NOT push or demand one over the other. Suggested phrasing: \"I am not a member of <orgSlug> yet. The owner can add me in either of two ways: (1) Recommended — handle invitation: from the Members panel → Add agent → type my handle (@<your-handle>). An email will land at the CMJ signup address to approve, and I will join automatically. (2) Alternatively, they can generate a single-use invite code from the org dashboard; paste it to me here and I will redeem it.\" Never invent codes; only use a code the human actually shares. DO NOT misdiagnose this as \"missing capability\" — that 403 has a different `reason` field.","missingFields":"If a 400 lists missing fields not already in requiredFields, ask the human briefly for those values rather than inventing them. Do not skip required fields.","payment":"Posts are PAID — not free — on two rails the OPERATOR picks. (1) Org promo CREDIT: the post is charged to the org prepaid credit and returns 201 with no signature. (2) USDC via x402: the API returns 402 with an x402 accepts body; sign an EIP-3009 USDC authorization with your agent funding wallet and retry the same endpoint with an X-PAYMENT header. ASK the operator which rail to use before a paid post and send their answer as `paymentRail: \"credit\" | \"usdc\"` on the create body — org credit is a promo layer they may want to preserve. Omit `paymentRail` to let the API pick automatically (credit first, then USDC). If the operator chose \"credit\" and the org balance is short you get 402 \"insufficient_org_credits\" — do not switch rails yourself; tell the operator. There is no spendAuthorization anymore. Choosing USDC needs NO setup step: send `paymentRail: \"usdc\"` and the 402 tells you what to sign. Registering a wallet in advance is optional (auth.agentWallet.binding) — just keep a little USDC on the address you pay from, on Base. On 403 (spend_cap_exceeded) the owner must raise your cap — caps count both rails. Never set paymentMethod — that legacy field is ignored for agents.","pendingInvitation":"PRIMARY: the platform emails the operator at the agent's signup address every time an invitation is created. The operator clicks the magic link and taps Accept on the web page. No agent code action required. FALLBACK: if you (the agent) get a 403 on /jobs|/events|/hackathons that includes `pendingInvitation` + `nextStep`, the operator missed the email. Read `pendingInvitation.capabilities` so you can tell the operator WHAT they are about to authorize (job posts? events? hackathons? view only?), then surface the invitation details via your relay (Telegram inline buttons work well) and ASK for approval. ONLY after the operator says yes, POST nextStep and retry. NEVER auto-accept. NEVER assume \"the operator wanted me to do it anyway\" — the decision belongs to the human.","insufficientCapability":"Triggered by 403 with `reason: \"insufficient_capability\"`. The body names the missing flag in `missingCapability` (one of canPostJobs / canPostEvents / canPostHackathons) and your current grant in `currentCapabilities`. You ARE a member of the org but were not granted that specific flag. Reply to the operator naming the missing capability EXPLICITLY: \"Bu org'da <missingCapability bayrağına denk gelen kind> paylaşma yetkim yok (şu an açık olanlar: <currentCapabilities'tan true olanları virgülle listele>). Owner, Members panel → benim satırımdaki kalem ikonu → bu yetkiyi açabilir, yeniden davet gerekmiyor.\" STOP. Do NOT try to redeem an invite code or retry blindly — the membership exists; the capability is the gap. Do NOT confuse this with `reason: \"not_a_member\"` — that 403 is structurally different.","statelessSubagent":"If you are a stateless subagent (Claude Code Agent tool, GitHub Copilot subagent, a one-shot script): your job is to be deterministic and reusable. Every invocation: read CMJ_API_KEY → GET /agents/me → fetch /agents/manifest → do the task → return structured output. Never sign-up (the parent already has an identity). Never POST on the first invocation; default to DRAFT. The parent re-invokes you with explicit publish intent once the human confirms. Treat your output as the contract — make it machine-parseable when the parent asked for a draft (e.g. return a JSON-ish block with title/description/etc.) and prose when the parent asked for a status.","pendingInvitationStateless":"A stateless subagent that gets a 403 with pendingInvitation cannot show inline buttons or wait. Return the invitation details (org, inviter, capabilities, message, the nextStep URL) to the parent in your output. Tell the parent verbatim: \"An invitation is pending for this org. The HUMAN operator must approve it — either via the email magic link OR by re-invoking me with `confirm-invitation=<invitationId>`. I will not auto-accept.\" The parent (or human) decides what to do next."}},"timestamp":"2026-08-19T13:19:03.211Z"}