Skip to content

Latest commit

 

History

History
759 lines (635 loc) · 26.6 KB

File metadata and controls

759 lines (635 loc) · 26.6 KB

Public API Examples

MergeWork exposes read-only API and MCP hosts for contributors and agents:

API_HOST=https://api.mrwk.ltclab.site
MCP_HOST=https://mcp.mrwk.ltclab.site

Status And Bounties

Check service status and list bounties:

curl -s "$API_HOST/api/v1/status"
curl -s "$API_HOST/api/v1/bounties"
curl -s "$API_HOST/api/v1/bounties?status=open"
curl -s "$API_HOST/api/v1/bounties?status=open&sort=available&limit=5"
curl -s "$API_HOST/api/v1/bounties/summary?status=open&q=proof"
curl -s "$API_HOST/api/v1/bounties/summary?status=open&sort=awards&limit=5"

The bounties list returns public bounty rows. status can be omitted or set to open, paid, or closed:

{
  "id": 36,
  "repo": "ramimbo/mergework",
  "issue_number": 164,
  "issue_url": "https://github.com/ramimbo/mergework/issues/164",
  "title": "MRWK bounty: contributor activity and bounty discovery improvements",
  "reward_mrwk": "100",
  "available_mrwk": "100",
  "reserved_mrwk": "500",
  "max_awards": 5,
  "awards_paid": 4,
  "awards_remaining": 1,
  "status": "open",
  "acceptance": "Focused public-facing enhancements that help contributors find bounties, inspect accepted work, or understand proof/account activity, with tests. Duplicate, marketing-only, docs-only, broad redesign, or unrelated changes do not qualify.",
  "created_at": "2026-05-24T20:44:00.015953"
}

Use id for the single-bounty API path. Use issue_number and issue_url when linking back to the source GitHub issue. Award counters can change as accepted work is paid; refresh concrete examples against the live API before relying on available slot counts.

Use sort to choose the bounty order: newest is the default, reward sorts by per-award reward, available sorts by the remaining MRWK pool, and awards sorts by remaining award slots. Use limit from 1 to 200 to cap returned rows after filtering and sorting.

Use /api/v1/bounties/summary with the same optional status, q, sort, and limit filters when an agent only needs capacity totals instead of full bounty rows:

{
  "bounties_shown": 1,
  "open_awards": 2,
  "open_pool_mrwk": "50"
}

Read a single bounty with its internal id from /api/v1/bounties:

curl -s "$API_HOST/api/v1/bounties/<bounty_id>"

The <bounty_id> value is the MergeWork bounty id, not the GitHub issue number. For example, an issue URL ending in /issues/22 may have a different API path such as /api/v1/bounties/11.

Advisory Attempt Reservations

Agents can register short-lived active attempts before opening a bounty PR so other contributors can inspect overlapping work. Attempt registration and release require a GitHub-authenticated browser/API session, and any submitter_account in the request body must match that authenticated GitHub login. Attempts are advisory only: they do not create payments, claim acceptance, mutate ledger balances, or stop maintainers from accepting useful work.

List active attempts for a bounty:

curl -s "$API_HOST/api/v1/bounties/<bounty_id>/attempts"

The list response returns the bounty id, advisory warnings, and active attempt reservations:

{
  "bounty_id": 65,
  "warnings": [],
  "attempts": [
    {
      "id": 12,
      "bounty_id": 65,
      "submitter_account": "github:tatelyman",
      "source_url": "https://github.com/ramimbo/mergework/tree/attempt-bounty-321",
      "status": "active",
      "expires_at": "2026-05-26T22:07:00+00:00",
      "created_at": "2026-05-25T22:07:00+00:00",
      "updated_at": "2026-05-25T22:07:00+00:00"
    }
  ]
}

Include expired or released attempts when auditing abandoned work:

curl -s "$API_HOST/api/v1/bounties/<bounty_id>/attempts?include_expired=true"

Register an attempt with a submitter identity, optional source URL, and TTL:

curl -s -X POST "$API_HOST/api/v1/bounties/<bounty_id>/attempts" \
  -H "Content-Type: application/json" \
  -d '{"submitter_account":"github:tatelyman","source_url":"https://github.com/ramimbo/mergework/tree/attempt-bounty-321","ttl_seconds":86400}'

Successful registration returns the attempt plus warnings when multiple active attempts exist:

{
  "status": "registered",
  "attempt": {
    "id": 12,
    "bounty_id": 53,
    "submitter_account": "github:tatelyman",
    "source_url": "https://github.com/ramimbo/mergework/tree/attempt-bounty-321",
    "status": "active",
    "expires_at": "2026-05-26T22:07:00+00:00",
    "created_at": "2026-05-25T22:07:00+00:00",
    "updated_at": "2026-05-25T22:07:00+00:00"
  },
  "warnings": []
}

If the same submitter already has an unexpired active attempt on the bounty, the API returns 409 duplicate_active_attempt. Closed, paid, or exhausted bounties return 409 not_available with warnings such as bounty is paid or bounty has no award slots remaining.

Release an active attempt when you stop working:

curl -s -X POST "$API_HOST/api/v1/bounty-attempts/<attempt_id>/release" \
  -H "Content-Type: application/json" \
  -d '{"submitter_account":"github:tatelyman"}'

Ledger, Proofs, Accounts, And Wallets

Check whether the current request has an authenticated GitHub session:

curl -s "$API_HOST/api/v1/auth/me"

Unauthenticated requests return a public session shape with a null login:

{
  "authenticated": false,
  "github_login": null
}

Read recent ledger entries and inspect one entry:

curl -s "$API_HOST/api/v1/ledger?limit=10"
curl -s "$API_HOST/api/v1/ledger/<sequence>"

Ledger entries use the internal immutable sequence number as the API path key. Recent-list and single-entry responses share the same shape:

{
  "sequence": 329,
  "type": "bounty_reserve",
  "from": "treasury:mrwk",
  "to": "reserve:bounty:36",
  "amount_mrwk": "500",
  "reference": "https://github.com/ramimbo/mergework/issues/164",
  "previous_hash": "25c9c46690780ffc5fe49a71c29c9d6343fe4ecbf9d0b98b56ce9dc5c94dd58a",
  "entry_hash": "248e1e38f90ac42897486a2b52a938ad51f31849250c4a979358e9721ec7c64e",
  "proof_hash": null,
  "created_at": "2026-05-24T20:44:00.019706"
}

proof_hash is null for non-proof ledger entries such as bounty reserves. It contains a proof hash for bounty-payment ledger entries that have a public proof.

Read accepted-work activity summarized from proof-backed bounty payments:

curl -s "$API_HOST/api/v1/activity"
curl -s "$API_HOST/api/v1/activity?q=p3xill"

The optional q parameter filters activity rows by account, amount, submission URL, proof hash, bounty repo, bounty issue URL, internal bounty id, or GitHub issue number. The response groups matching proof-backed bounty payments into totals, contributor rollups, and the most recent payment rows:

{
  "totals": {
    "accepted_awards": 2,
    "accepted_mrwk": "115",
    "contributors": 1
  },
  "query": "p3xill",
  "contributors": [
    {
      "account": "github:p3xill",
      "accepted_awards": 2,
      "accepted_mrwk": "115",
      "latest_submission_url": "https://github.com/ramimbo/mergework/pull/226#pullrequestreview-4354910919",
      "latest_bounty_repo": "ramimbo/mergework",
      "latest_bounty_issue_number": 219,
      "latest_bounty_issue_url": "https://github.com/ramimbo/mergework/issues/219",
      "latest_proof_hash": "99f78d41b9a493ba2e6136cba0b0762f013a913c9d90c562976282e93d00b81f",
      "latest_proof_url": "/proofs/99f78d41b9a493ba2e6136cba0b0762f013a913c9d90c562976282e93d00b81f"
    }
  ],
  "recent": [
    {
      "ledger_sequence": 399,
      "account": "github:p3xill",
      "amount_mrwk": "40",
      "submission_url": "https://github.com/ramimbo/mergework/pull/226#pullrequestreview-4354910919",
      "proof_hash": "99f78d41b9a493ba2e6136cba0b0762f013a913c9d90c562976282e93d00b81f",
      "proof_url": "/proofs/99f78d41b9a493ba2e6136cba0b0762f013a913c9d90c562976282e93d00b81f",
      "bounty_repo": "ramimbo/mergework",
      "bounty_id": 37,
      "bounty_issue_number": 219,
      "bounty_issue_url": "https://github.com/ramimbo/mergework/issues/219",
      "bounty_url": "/bounties/37",
      "created_at": "2026-05-25T08:25:28.316705"
    }
  ]
}

contributors is sorted by accepted MRWK amount, while recent is sorted by newest ledger sequence and capped to the latest 100 matching rows. Use proof_hash with /api/v1/proofs/<proof_hash> to inspect the public proof payload for a payment.

Inspect a proof, account, or registered wallet:

curl -s "$API_HOST/api/v1/proofs/<proof_hash>"
curl -s "$API_HOST/api/v1/accounts/treasury:mrwk"
curl -s "$API_HOST/api/v1/wallets/<wallet_address>"

The wallet endpoint is a read-only wallet lookup. It returns the registered address, public key, optional label and linked GitHub login, current balance, current nonce, next nonce to sign with, and registration timestamp:

{
  "address": "mrwk1fb1437aec45b46ec640f44b2e2aced55dc23556e",
  "public_key_hex": "d88d3edf935ba932ee2737ee5500c795f21caeb4a2fdeacb55a4ff63c52c9d51",
  "label": null,
  "github_login": "prettyboyvic",
  "balance_mrwk": "50",
  "nonce": 2,
  "next_nonce": 3,
  "created_at": "2026-05-24T17:50:56.118158"
}

Account responses identify the normalized ledger address, optional GitHub login, existence, current balance, accepted-work summary, and whether the account can move funds directly:

{
  "account": "github:tatelyman",
  "ledger_address": "github:tatelyman",
  "github_login": "tatelyman",
  "exists": true,
  "balance_mrwk": "395",
  "transfer_status": "Claim GitHub balances from /me after linking a registered mrwk1 wallet.",
  "accepted_work": {
    "accepted_awards": 5,
    "accepted_mrwk": "395",
    "latest_ledger_sequence": 42,
    "latest_submission_url": "https://github.com/ramimbo/mergework/pull/183",
    "latest_proof_hash": "a29b9cf54f2ea4734d58e9371b20234f85936e95bd8c45687f0644ad6a9e6871",
    "latest_proof_url": "/proofs/a29b9cf54f2ea4734d58e9371b20234f85936e95bd8c45687f0644ad6a9e6871"
  }
}

For treasury: and reserve: accounts, github_login is null and transfer_status explains that direct MRWK wallet transfers are only available for registered mrwk1 addresses.

Read the proof-backed accepted-work list for a single account:

curl -s "$API_HOST/api/v1/accounts/github:carpedkm/accepted-work"

The response includes the account summary plus the same accepted-work rows used by the public account page, so agents can inspect recent proof, ledger, submission, source issue, internal bounty id and public bounty URL, and maintainer acceptance details without scraping HTML:

{
  "account": "github:carpedkm",
  "summary": {
    "accepted_awards": 6,
    "accepted_mrwk": "340",
    "latest_ledger_sequence": 682,
    "latest_submission_url": "https://github.com/ramimbo/mergework/issues/407#issuecomment-4545035155",
    "latest_proof_hash": "cb7707861ca88447db67aa707d06ca51f4d6a1b382cbba33305b251f88fd1e80",
    "latest_proof_url": "/proofs/cb7707861ca88447db67aa707d06ca51f4d6a1b382cbba33305b251f88fd1e80"
  },
  "accepted_work": [
    {
      "ledger_sequence": 682,
      "ledger_url": "/ledger/682",
      "proof_hash": "cb7707861ca88447db67aa707d06ca51f4d6a1b382cbba33305b251f88fd1e80",
      "proof_url": "/proofs/cb7707861ca88447db67aa707d06ca51f4d6a1b382cbba33305b251f88fd1e80",
      "amount_mrwk": "50",
      "submission_url": "https://github.com/ramimbo/mergework/issues/407#issuecomment-4545035155",
      "issue_url": "https://github.com/ramimbo/mergework/issues/407",
      "repo": "ramimbo/mergework",
      "issue_number": 407,
      "bounty_id": 67,
      "bounty_url": "/bounties/67",
      "accepted_by": "ramimbo",
      "created_at": "2026-05-26T15:30:01.346962"
    }
  ]
}

Register a wallet public key. Keep the private key local; only send the public key to MergeWork.

curl -s -X POST "$API_HOST/api/v1/wallets/register" \
  -H "Content-Type: application/json" \
  -d '{"public_key_hex":"<64 lowercase hex chars>","label":"agent wallet"}'

The registration response uses the same public wallet shape as /api/v1/wallets/<address>:

{
  "address": "mrwk102d449a31fbb267c8f352e9968a79e3e5fc95c1b",
  "public_key_hex": "1111111111111111111111111111111111111111111111111111111111111111",
  "label": "agent wallet",
  "github_login": null,
  "balance_mrwk": "0",
  "nonce": 0,
  "next_nonce": 1,
  "created_at": "2026-05-24T20:00:00"
}

Link a registered wallet to the current GitHub login. The GitHub login comes from the signed-in session cookie, not the request body. Sign the canonical wallet-link payload for the wallet's next_nonce with the wallet private key; do not send the private key to MergeWork. The signed payload is compact ASCII JSON with sorted keys and includes the authenticated GitHub login:

{"address":"<registered_mrwk1_address>","github_login":"<signed_in_github_login>","nonce":1,"type":"mrwk_link_github_v1"}
curl -s -X POST "$API_HOST/api/v1/wallets/link-github" \
  -H "Content-Type: application/json" \
  -b "<signed GitHub session cookie>" \
  -d '{"address":"<registered_mrwk1_address>","nonce":1,"signature_hex":"<128 lowercase hex chars>"}'

The link response uses the same public wallet shape as /api/v1/wallets/<address> with github_login set to the authenticated login:

{
  "address": "mrwk102d449a31fbb267c8f352e9968a79e3e5fc95c1b",
  "public_key_hex": "1111111111111111111111111111111111111111111111111111111111111111",
  "label": "agent wallet",
  "github_login": "tatelyman",
  "balance_mrwk": "0",
  "nonce": 1,
  "next_nonce": 2,
  "created_at": "2026-05-24T20:00:00"
}

Claim an authenticated GitHub account balance into a linked wallet. The GitHub login comes from the signed-in session cookie, not from the request body. Sign the canonical GitHub-claim payload with the linked wallet private key and the wallet's next_nonce value; do not send the private key to MergeWork. This example assumes the wallet was just linked with nonce 1, so its next nonce is 2. The signed payload is compact ASCII JSON with sorted keys and includes the authenticated GitHub login:

{"address":"<linked_mrwk1_address>","github_login":"<signed_in_github_login>","nonce":2,"type":"mrwk_claim_github_v1"}
curl -s -X POST "$API_HOST/api/v1/github/claim" \
  -H "Content-Type: application/json" \
  -b "mrwk_user=<signed-session-cookie>" \
  -d '{"address":"<linked_mrwk1_address>","nonce":2,"signature_hex":"<128 lowercase hex chars>"}'

Successful claim responses use the same immutable ledger-entry shape as /api/v1/ledger/<sequence>:

{
  "sequence": 42,
  "type": "github_claim",
  "from": "github:<github_login>",
  "to": "<linked_mrwk1_address>",
  "amount_mrwk": "<claimed_amount_mrwk>",
  "reference": "github-claim:<github_login>:<linked_mrwk1_address>:2",
  "previous_hash": "248e1e38f90ac42897486a2b52a938ad51f31849250c4a979358e9721ec7c64e",
  "entry_hash": "d0c0e8f63ad11f2cc6e5f10dc1f61c45f943f3ab126c45761283c0ccf04cb276",
  "proof_hash": null,
  "created_at": "2026-05-24T20:05:00+00:00"
}

MCP Examples

List MCP tools:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Call get_balance:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_balance","arguments":{"account":"treasury:mrwk"}}}'

Call list_bounties:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_bounties","arguments":{}}}'

Call get_bounty with the internal bounty id returned by list_bounties, not the GitHub issue number:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"get_bounty","arguments":{"id":11}}}'

Call list_bounty_attempts with the same internal bounty id before opening a PR. Omit include_expired to see only active attempts:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"list_bounty_attempts","arguments":{"bounty_id":11,"include_expired":false}}}'

Call get_proof with the proof hash returned by /api/v1/ledger, /api/v1/activity, or get_ledger_entry:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"get_proof","arguments":{"hash":"<proof_hash>"}}}'

Call submit_wallet_transfer with the same signed transfer fields used by the REST transfer API. Sign the canonical wallet transfer payload locally; do not send private keys to MergeWork:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"submit_wallet_transfer","arguments":{"from_address":"<sender_mrwk1_address>","to_address":"<receiver_mrwk1_address>","amount_mrwk":"1.5","nonce":3,"memo":"agent payout consolidation","signature_hex":"<128 lowercase hex chars>"}}}'

Successful MCP transfer responses wrap a JSON-string transfer object in the first content block. Parse result.content[0].text to read the transfer hash, ledger sequence, addresses, amount, nonce, memo, and timestamp:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"hash\":\"9d0d922d25ae3c6045d9c1d64af9657228c00f925f52e4f447d4b451d91b6278\",\"type\":\"wallet_transfer\",\"ledger_sequence\":42,\"from_address\":\"mrwk102d449a31fbb267c8f352e9968a79e3e5fc95c1b\",\"to_address\":\"mrwk1fb1437aec45b46ec640f44b2e2aced55dc23556e\",\"amount_mrwk\":\"1.5\",\"nonce\":3,\"memo\":\"agent payout consolidation\",\"created_at\":\"2026-05-24T20:05:00\"}"
      }
    ]
  }
}

The get_proof MCP response uses JSON-RPC content blocks. The first content block is a JSON string with proof metadata plus the stored public proof payload:

{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"hash\":\"<proof_hash>\",\"kind\":\"bounty_payment\",\"ledger_sequence\":322,\"bounty_id\":32,\"submission_id\":279,\"created_at\":\"2026-05-24T20:28:53.628707\",\"proof\":{\"kind\":\"bounty_payment\",\"repo\":\"ramimbo/mergework\",\"issue_number\":156,\"bounty_id\":32,\"submission_url\":\"https://github.com/ramimbo/mergework/pull/155#pullrequestreview-4353350771\",\"to_account\":\"github:ckeplinger199\",\"amount_mrwk\":\"40\"}}"
      }
    ]
  }
}

In that MCP payload, bounty_id is the internal MergeWork bounty id. The proof.issue_number value is the source GitHub issue number when the proof was created from a GitHub bounty claim.

Call get_ledger_entry with the immutable ledger sequence returned by /api/v1/ledger, /api/v1/activity, get_bounty award rows, or get_proof. The MCP response wraps the same ledger-entry shape as /api/v1/ledger/<sequence> in result.content[0].text:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"get_ledger_entry","arguments":{"sequence":42}}}'
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"sequence\":42,\"type\":\"bounty_payment\",\"from\":\"reserve:bounty:11\",\"to\":\"github:tatelyman\",\"amount_mrwk\":\"75\",\"reference\":\"https://github.com/ramimbo/mergework/pull/183\",\"previous_hash\":\"248e1e38f90ac42897486a2b52a938ad51f31849250c4a979358e9721ec7c64e\",\"entry_hash\":\"d0c0e8f63ad11f2cc6e5f10dc1f61c45f943f3ab126c45761283c0ccf04cb276\",\"proof_hash\":\"a29b9cf54f2ea4734d58e9371b20234f85936e95bd8c45687f0644ad6a9e6871\",\"created_at\":\"2026-05-24T20:05:00\"}"
      }
    ]
  }
}

Call get_wallet with a registered mrwk1 address when an agent needs wallet metadata through MCP. The response wraps the same public wallet shape as /api/v1/wallets/<wallet_address>; unknown but well-formed wallet addresses return wallet not found:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"get_wallet","arguments":{"address":"<wallet_address>"}}}'
{
  "jsonrpc": "2.0",
  "id": 9,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"address\":\"<wallet_address>\",\"public_key_hex\":\"<64 lowercase hex chars>\",\"label\":\"MCP wallet\",\"github_login\":null,\"balance_mrwk\":\"0\",\"nonce\":0,\"next_nonce\":1,\"created_at\":\"2026-05-24T20:00:00\"}"
      }
    ]
  }
}

Call submit_work_proof with format:"json" when an agent needs machine-readable bounty submission guidance. Use either internal bounty_id or GitHub issue_number; include repo with issue_number when the issue number could exist in more than one repository:

curl -s -X POST "$MCP_HOST/mcp" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"submit_work_proof","arguments":{"issue_number":404,"repo":"ramimbo/mergework","format":"json"}}}'

Structured responses include both a JSON-string copy in result.content[0].text and the parsed object in result.structuredContent. Read availability, can_submit, availability_warnings, submission_requirements.reference_formats, and submission_requirements.next_actions before opening or claiming work:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"bounty_id\":64,\"issue_number\":404,\"status\":\"open\",\"availability\":\"open_for_submissions\",\"can_submit\":true,\"availability_warnings\":[],\"awards_remaining\":8,\"max_awards\":30,\"awards_paid\":22,\"reward_mrwk\":\"40\",\"available_mrwk\":\"320\",\"repository\":\"ramimbo/mergework\",\"issue_url\":\"https://github.com/ramimbo/mergework/issues/404\",\"title\":\"MRWK bounty: review open MergeWork PRs with evidence, round 11\",\"submission_requirements\":{\"reference_formats\":[\"Bounty #404\",\"Refs #404\"],\"claim_command\":\"/claim\",\"attempt_endpoint\":\"/api/v1/bounties/64/attempts\",\"next_actions\":[{\"id\":\"confirm_award_slot\",\"required\":true,\"text\":\"Confirm this bounty is open and has at least one award slot remaining.\"}]}}"
      }
    ],
    "structuredContent": {
      "bounty_id": 64,
      "issue_number": 404,
      "status": "open",
      "availability": "open_for_submissions",
      "can_submit": true,
      "availability_warnings": [],
      "awards_remaining": 8,
      "max_awards": 30,
      "awards_paid": 22,
      "reward_mrwk": "40",
      "available_mrwk": "320",
      "repository": "ramimbo/mergework",
      "issue_url": "https://github.com/ramimbo/mergework/issues/404",
      "title": "MRWK bounty: review open MergeWork PRs with evidence, round 11",
      "submission_requirements": {
        "reference_formats": ["Bounty #404", "Refs #404"],
        "claim_command": "/claim",
        "attempt_endpoint": "/api/v1/bounties/64/attempts",
        "next_actions": [
          {
            "id": "confirm_award_slot",
            "required": true,
            "text": "Confirm this bounty is open and has at least one award slot remaining."
          }
        ]
      }
    }
  }
}

Pre-Bounty Preflight Checks

Before opening a PR or claiming a bounty, check the live API for award capacity and active attempts. These checks are read-only and do not create ledger entries, modify balances, or reserve awards.

Check Bounty Capacity

Use the bounties list or single-bounty endpoint to confirm a bounty is still open and has available awards:

# List all open bounties with their capacity
curl -s "$API_HOST/api/v1/bounties?status=open"

# Quick capacity summary
curl -s "$API_HOST/api/v1/bounties/summary?status=open"

# Inspect one bounty by its internal id (from /api/v1/bounties)
curl -s "$API_HOST/api/v1/bounties/<bounty_id>"

The single-bounty response includes max_awards, awards_paid, and awards_remaining:

{
  "id": 36,
  "issue_number": 164,
  "reward_mrwk": "100",
  "max_awards": 5,
  "awards_paid": 4,
  "awards_remaining": 1,
  "status": "open"
}

Do not open a PR if awards_remaining is zero, or if the bounty status is paid or closed.

Check Active Attempts

Before registering a new attempt or opening a PR, inspect existing active attempts for the same bounty:

curl -s "$API_HOST/api/v1/bounties/<bounty_id>/attempts"

The response returns the same wrapper object used by the active-attempts API:

{
  "bounty_id": 65,
  "warnings": [],
  "attempts": [
    {
      "id": 12,
      "bounty_id": 65,
      "submitter_account": "github:tatelyman",
      "source_url": "https://github.com/ramimbo/mergework/tree/attempt-bounty-321",
      "status": "active",
      "expires_at": "2026-05-26T22:07:00+00:00",
      "created_at": "2026-05-25T22:07:00+00:00",
      "updated_at": "2026-05-25T22:07:00+00:00"
    }
  ]
}

If another active attempt already covers your exact intended scope, pick a different scope or bounty rather than racing with a duplicate PR. Expired or released attempts can be included for abandoned-work audit:

curl -s "$API_HOST/api/v1/bounties/<bounty_id>/attempts?include_expired=true"

Verify Open PRs for the Same Bounty Issue

Cross-reference open GitHub PRs against the bounty issue number. Opening multiple PRs for the same bounty issue from different contributors is normal for multi-award bounties, but you should avoid overlapping scope:

# Check open PRs referencing the same bounty issue via the GitHub API (use pulls endpoint, not issues)
curl -s -H "Accept: application/vnd.github+json" \
  "https://api.github.com/repos/ramimbo/mergework/pulls?state=open&per_page=50"

Filter by PR body references (Bounty #N or Refs #N) to find scope-alike PRs before opening new work.

Avoid Exhausted, Paid, and Stale Rounds

Before opening work on a bounty round:

  1. Check the live bounty API — if status is not "open" or awards_remaining is zero, the round is exhausted or closed and no new work will be accepted.
  2. Check the GitHub issue state — closed issues cannot receive new PR rewards.
  3. Check for recent maintainer comments — if a maintainer has marked the bounty as superseded or redirected work elsewhere, that is authoritative.
  4. Verify stale rounds — a round is stale when the bounty text, latest maintainer comment, or open PR queue suggests the requested work is already handled, no longer needed, or no longer being reviewed. Do not target stale rounds unless a maintainer explicitly redirects the work.

Use the live API over stale issue text when checking award capacity on multi-award bounties: the API reflects current payment state, while the issue body may describe the initial offer.