{"manifestVersion":9,"product":"Priv Life","api":"https://priv-life.com/org-api","docs":"https://priv-life.com/llms.txt","auth":{"kind":"agent-key","header":"Authorization: Bearer pl_live_<orgId>_<keyId>_<secret>","mintedAt":"https://priv-life.com/agents/","model":"A key acts as the person who minted it. Its powers are the intersection of the key’s grant and that person’s own, recomputed on every request, so a key cannot outgrow its person and revoking it is immediate.","scope":"A key may be narrowed to one group or one league; `scoped` tools then work inside it and nowhere else.","undelegatable":{"note":"These capabilities cannot be put on a key even by somebody who holds them. The mint refuses them with the code `undelegatable`, and the door drops them again.","capabilities":[{"capability":"rigcheck.perform","reason":"Walking a vehicle check is an attestation that a human physically looked at the truck. A machine cannot look."},{"capability":"rigcheck.resolve","reason":"Closing a defect says somebody dealt with it, which is the same species of claim as walking the check."}]}},"conventions":{"paths":"Every tool carries an absolute `path`. Most are under `/org-api/`; Vote’s three are under `/vote-api/`, because Vote is a separate service that verifies your key with this one. Join a path to the site origin, never to `api`.","idempotency":"Every write accepts `Idempotency-Key: <uuid>`. A repeat with the same key replays the original response rather than acting twice.","errors":"Every error carries a machine `code`. A 403 carries the `capability` it wanted, a 400 carries `fields[]` with the path of each bad input, and a `next` appears wherever there is one.","dryRun":"Where a tool lists `dryRun`, it defaults to TRUE and returns the plan having written nothing.","plan":"Show the person the dry run and let them approve it. That is the difference between delegating a job and hoping.","rateLimits":"Each key is limited per minute, counted separately for reads and writes. Over the limit is a 429 with `Retry-After` and a `retryAfter` in seconds: wait that long rather than retrying at once.","confirmation":"A tool listing `confirmation` needs a person to say yes to THAT act, not merely the capability. The 403 carries `confirmToken` and a sentence; a person approves it in the Agents app; you then repeat the identical request. Approval binds to the exact method, path and body, is single use, and lapses in 30 minutes. Nothing has happened when you get the 403.","humanOnly":"An agent sets things up and reads them; a human operates them. `humanOnly` lists the acts refused to every key with the code `human_only` — attestations, decisions a body made in a room, and taking on standing. Do not retry them and do not ask for more capabilities; relay the reason to the person instead.","visibility":"Everything a key does is recorded as the delegating person’s work and marked as an agent’s, and they can read it back. Act as if it will be reviewed, because it will be."},"mcp":{"url":"https://priv-life.com/org-api/mcp","transport":"streamable-http","auth":"The same agent key, as a bearer token. Stateless: no session id, no stream to open."},"tools":[{"name":"setup_league","method":"POST","capability":"teams.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Create a whole league — the league, its fields, its teams, its players and its schedule — in one transaction.","detail":"Send one document: league{name,sport,ageGroup,seasonName,startsOn,endsOn}, fields[], teams[], players[] (each naming its team by name), schedule{startsAt,intervalDays,doubleRound}. `dryRun` defaults to TRUE and returns the plan having written nothing: run it, show the person the summary, and only then send `dryRun:false`. It never half-applies.","app":"teams","path":"/org-api/orgs/{orgId}/agent/leagues/setup"},{"name":"list_leagues","method":"GET","capability":"teams.read","scoped":true,"summary":"Every league in the organization, with its season and team count.","app":"teams","path":"/org-api/orgs/{orgId}/leagues"},{"name":"get_league","method":"GET","capability":"teams.read","scoped":true,"summary":"One league in full: teams, rosters, fields, games, standings, draft state.","detail":"The read to start from. It answers in one call what would otherwise be six, so an agent asked \"what is the state of my league\" should call this and nothing else.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}"},{"name":"create_league","method":"POST","capability":"teams.manage","scoped":false,"writes":true,"summary":"Create an empty league in a season.","detail":"Prefer `setup_league`. This exists for a league being built a piece at a time, and it fails with `season_name_taken` where the setup document would quietly pick a free name.","app":"teams","path":"/org-api/orgs/{orgId}/leagues"},{"name":"add_team","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Add a team to a league.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/teams"},{"name":"add_player","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Add a player to the league pool, creating the person if they are new.","detail":"Name alone is enough; email and phone are optional. A player in the pool is not yet on a team — `assign_player` puts them on one.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/players"},{"name":"assign_player","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Put a player from the pool onto a team, or move them between teams.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/players/{personId}/team"},{"name":"add_field","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Add a field or venue the league plays on, with its usage and windows.","detail":"The schedule generator can only place games on fields that exist, so this comes first.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/fields"},{"name":"generate_schedule","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Generate the round-robin schedule from the teams, the fields and a start date.","detail":"Needs at least two teams and one field. `clear-schedule` first if one already exists — this adds, it does not replace.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/generate-schedule"},{"name":"get_schedule","method":"GET","capability":"teams.read","scoped":true,"summary":"Games, practices and bookings in a date range, with fields and scores.","app":"teams","path":"/org-api/orgs/{orgId}/schedule"},{"name":"record_result","method":"PATCH","capability":"teams.manage","scoped":true,"writes":true,"summary":"Record a game score, or move a game to another time or field.","detail":"A league game is scored as `homeScore` and `awayScore`. Standings follow from it.","app":"teams","path":"/org-api/orgs/{orgId}/schedule/{eventId}"},{"name":"get_standings","method":"GET","capability":"teams.read","scoped":true,"summary":"The table: played, won, lost, drawn, runs or goals for and against, and the differential, grouped by division.","detail":"Pass `leagueId` for one league’s table, which is what anybody in a club running five of them means; `seasonId` narrows to a season. A game counts once both scores are in and never when it was canceled. A league’s own page (`get_league`) carries the same table plus the hand-kept corrections and streak badges a commissioner typed.","app":"teams","path":"/org-api/orgs/{orgId}/standings"},{"name":"publish_league_page","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Publish the league’s public page — schedule and standings on a link anyone can read.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/page"},{"name":"open_next_season","method":"POST","capability":"teams.manage","scoped":true,"writes":true,"summary":"Open next season on an existing league, optionally copying its teams and rosters.","detail":"Nothing is archived: the current season keeps its games and state, and the league’s tabs switch to the new one.","app":"teams","path":"/org-api/orgs/{orgId}/leagues/{leagueId}/seasons/open"},{"name":"list_fields","method":"GET","capability":"teams.read","scoped":true,"summary":"Every field the organization plays on, with its windows and what is booked.","app":"teams","path":"/org-api/orgs/{orgId}/venues"},{"name":"get_org","method":"GET","capability":"people.read","scoped":false,"summary":"What is set up in this organization: membership types, custom fields, groups, the dues policy, and how many people are on the roster.","detail":"Call this before either setup document. It decides whether one is wanted at all — an organization that already has its types and fields wants a roster import, not a configuration it would be told was a duplicate line by line.","app":"members","path":"/org-api/orgs/{orgId}/agent/org"},{"name":"setup_org","method":"POST","capability":"org.settings","scoped":false,"writes":true,"dryRun":true,"summary":"Configure an organization in one transaction: its custom fields, its membership types, its groups and its dues policy.","detail":"Send fields[], membershipTypes[], groups[] and settings{approvalRequired, dues{enabled,graceDays,lateAction,backDues}}. Fields are applied before types, because a membership type may only require a certification that exists. Anything already there is reported as already there rather than failing the document, so running it twice is safe. Including `groups` also needs groups.manage. It does NOT create the organization — a person founds that themselves, see `humanOnly`.","app":"members","path":"/org-api/orgs/{orgId}/agent/org/setup"},{"name":"import_roster","method":"POST","capability":"people.write","scoped":false,"writes":true,"dryRun":true,"summary":"Import a roster — the spreadsheet evening — matching people already here rather than doubling them.","detail":"Send `people` as rows of objects with whatever column names the sheet had, or `csv` with the file itself. A row whose email is already on the roster UPDATES that person. A row with no email whose NAME matches somebody here comes back `ambiguous` and is NOT imported: that is genuinely undecidable, because two people of one name is ordinary and so is one person typed twice. Show the candidates to the person — they add an email, send that row again with the right `personId`, or confirm with `ambiguous: \"create\"`. Guardian columns make a row a child’s profile, which carries no contact details of its own. A `team` column places people on their team. Every row comes back with what would happen to it.","app":"members","path":"/org-api/orgs/{orgId}/agent/roster/import"},{"name":"get_shift_setup","method":"GET","capability":null,"scoped":false,"summary":"What the schedule is set up for: qualifications and how many people hold each, assignment types, shift blocks, the weekly patterns, and whether any coverage exists yet.","detail":"Call this before `setup_shift_schedule`. It decides whether a document is wanted at all — an organization that already has its shifts and a default pattern wants demand generated for next month, not a configuration it would be told was a duplicate line by line. It writes nothing, unlike the schedule screen, so it is safe to poll.","app":"time","path":"/org-api/orgs/{orgId}/agent/shifts"},{"name":"setup_shift_schedule","method":"POST","capability":"time.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Set a duty schedule up in one transaction: the qualifications, the assignment types, the shift blocks, a weekly coverage pattern, and the demand generated from it for a date range.","detail":"Send qualifications[], assignmentTypes[], shiftBlocks[] (each `{name,start,end}` with \"18:00\"-style times — an end at or before the start is the next morning), template{name,makeDefault,coverage[{days,shift,qualification,assignmentType,count}]} and generate{from,to|days}. **The document names things rather than carrying ids**: `days` takes \"monday\", 1, or a shorthand like \"weekdays\"; `shift` and `qualification` name a block and a qualification from this same document or one already here. Lookups are created before the pattern is resolved, so one document can define the vocabulary and use it. A row that already exists is reported as already there and **never edited** — a \"Night\" block somebody set to 19:00 stays at 19:00, and the plan says so in `warnings`. `dryRun` defaults to TRUE. Demand is never written into the past.","app":"time","path":"/org-api/orgs/{orgId}/agent/shifts/setup"},{"name":"draft_coverage_request","method":"GET","capability":"time.manage","scoped":false,"summary":"The holes in the schedule for a window, who is qualified and free for each, and a drafted email asking for cover.","detail":"Writes nothing and sends nothing. It returns `holes`, `candidates` — who could take each one, most useful first — and `draft{subject,body,audience}` ready to hand to `send_message`, which needs a person to confirm it (R51). That split is deliberate: finding the gaps is arithmetic, and asking forty volunteers to give up a Saturday is a decision. The candidate list exists because mail goes to the organization or a group and not to five named people, so chasing three of them by telephone is often the better move.","app":"time","path":"/org-api/orgs/{orgId}/agent/shifts/coverage-request"},{"name":"get_coverage","method":"GET","capability":null,"scoped":false,"summary":"Demand versus signups for a date range: every slot, who is on it, and where the holes are.","detail":"Takes `from` and `to`. The same report the schedule screen draws, which also means a future date with no coverage on it materialises the organization’s default weekly pattern when it is asked about — so slots appearing on a first read is expected, not a write you made.","app":"time","path":"/org-api/orgs/{orgId}/shifts/gaps"},{"name":"add_signup","method":"POST","capability":null,"scoped":false,"writes":true,"summary":"Sign somebody up for a shift, against an open slot or ad hoc.","detail":"Send `slotId` from `get_coverage` with `assignmentTypeId`, `startMinutes` and `endMinutes`; or a `date`, `timeBlockId` and `roleId` for coverage nobody asked for. With no capability it signs up **the person this key acts for**, and only for a qualification they hold — signing anybody else up needs time.manage, which is the scheduler’s power. Times snap to the organization’s signup increment and must sit inside the block, and an overlap with that person’s own day is refused.","app":"time","path":"/org-api/orgs/{orgId}/shifts/signups"},{"name":"remove_signup","method":"DELETE","capability":"time.manage","scoped":false,"writes":true,"summary":"Take a signup off the schedule.","detail":"The scheduler’s call, not the volunteer’s: a member asks. It removes a plan rather than a record — the hours somebody actually worked are a different table and no key may write it at all.","app":"time","path":"/org-api/orgs/{orgId}/shifts/signups/{id}"},{"name":"get_board","method":"GET","capability":null,"scoped":false,"summary":"What Board holds here: the bodies and their seat counts, the next and last meetings with their minutes status, the resolutions register, and the business the board tabled.","detail":"Call this first. It decides which of the three Board tools is wanted — an organization with a year already on the calendar wants an agenda for the next meeting, not a schedule it would be told was a duplicate twelve times over. No minutes text travels in it: draft minutes are the secretary’s working copy.","app":"board","path":"/org-api/orgs/{orgId}/agent/board"},{"name":"setup_board_year","method":"POST","capability":"board.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Lay a year of meetings on the calendar in one transaction — the body, the recurring date, the time and place, and the standing agenda on every one of them.","detail":"Send body{name,create,kind}, schedule{pattern,from,months,time,durationMinutes,location,title} and standingAgenda[]. **The pattern is written the way the bylaws write it**: \"second tuesday\", \"last thursday\", or a day of the month like 15. `from` is a month (YYYY-MM); the pattern decides the day, and the month is appended to every title so twelve rows can be told apart. A month with no such day — a fifth Monday — is left without a meeting and said so in `warnings` rather than moved to another week. A meeting already on the calendar that day is reported as already there and **never re-timed**: somebody would arrive at seven to an empty room. Dates in the past are skipped. Standing agenda items are put on the meetings this document CREATES and on no others. `dryRun` defaults to TRUE. Creating a body also needs groups.manage.","app":"board","path":"/org-api/orgs/{orgId}/agent/board/setup"},{"name":"draft_agenda","method":"POST","capability":"board.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Draft the agenda for one meeting, derived from the record: the standing items this body used last time, approval of the previous minutes, and every motion still tabled.","detail":"Send nothing and the agenda is composed from what the record already knows — which is the point, because unfinished business is the item an agenda forgets and the one an agent cannot invent. Send `items[]` to add your own; `includeDerived` (true by default) decides whether the derived ones come too. **It appends.** Items already on the meeting keep their order and anything new goes after them, so a secretary who has shaped next week’s agenda does not watch it be replaced. `dryRun` defaults to TRUE.","app":"board","path":"/org-api/orgs/{orgId}/agent/board/meetings/{meetingId}/agenda"},{"name":"draft_minutes","method":"GET","capability":"board.manage","scoped":false,"summary":"The minutes skeleton for a meeting: every fact the record holds — the roll, the quorum arithmetic, each motion with its tally and resolution number — and a marked hole wherever only the transcript can speak.","detail":"Writes nothing. You have the transcript and the language; this has the facts you would otherwise invent. Fill ONLY the sections marked `needsTranscript` and leave attendance and motions exactly as they come — a transcript shows who spoke, not who was present or how they voted. Then save it with `save_draft_minutes`. If the roll was never recorded the attendance section comes back empty and says so: a person records it in Board, and writing one from a transcript is a claim nobody made.","app":"board","path":"/org-api/orgs/{orgId}/agent/board/meetings/{meetingId}/minutes-draft"},{"name":"save_draft_minutes","method":"PUT","capability":"board.manage","scoped":false,"writes":true,"summary":"Save minutes on a meeting as a DRAFT. Never as approved.","detail":"Send `text`, or `sections[{heading,body}]` to be rendered into it. It always stores a draft, whatever you send: approving minutes is human-only (R55), and a person does it in Board by recording the motion that approved them. Minutes a motion has already approved are locked — the answer is 409 `minutes_approved`, which means stop rather than retry.","app":"board","path":"/org-api/orgs/{orgId}/agent/board/meetings/{meetingId}/minutes"},{"name":"get_meeting","method":"GET","capability":null,"scoped":false,"summary":"One meeting in full: its agenda, the roll with the quorum line, every motion with its tally, and the packet.","detail":"The read that answers what happened, in one call. Draft minutes are included only for a caller holding board.manage — they are the secretary’s working copy until a motion approves them.","app":"board","path":"/org-api/orgs/{orgId}/meetings/{meetingId}"},{"name":"list_resolutions","method":"GET","capability":null,"scoped":false,"summary":"The resolutions register: what the organization has formally decided, numbered and citable years later.","detail":"Newest first, with the meeting each was decided at. A numbered resolution never changes — correcting one is a new resolution, which is a decision only the board can take.","app":"board","path":"/org-api/orgs/{orgId}/resolutions"},{"name":"setup_campaign","method":"POST","capability":"giving.manage","scoped":false,"writes":true,"dryRun":true,"confirmation":"publish","summary":"Set a fundraising campaign up — its name, goal, dates and the public giving page donors use — in one transaction.","detail":"Send campaign{name,goalCents,startsOn,endsOn,note} and optionally page{publish,blurb,suggestedCents,showProgress}. A campaign of that name already here is UPDATED and reported as already there, so running it twice is safe. `dryRun` defaults to TRUE. **Publishing is the gated part**: creating the campaign needs only giving.manage, but a document with `page.publish` is answered 403 `confirmation_required` with a sentence, because a public page asking strangers for money in the organization’s name is a person’s decision. Omit `page.publish` and nothing is gated. The page cannot take a gift until a person has connected the organization’s own PayPal account, and the plan says so in `warnings` when it has not been.","app":"giving","path":"/org-api/orgs/{orgId}/agent/giving/setup"},{"name":"list_campaigns","method":"GET","capability":"giving.read","scoped":false,"summary":"Every campaign, with its goal, what has been given, and whether it has a public page.","detail":"Call this before `setup_campaign`: a fund drive that already exists wants updating, not a second copy under a slightly different name.","app":"giving","path":"/org-api/orgs/{orgId}/campaigns"},{"name":"list_gifts","method":"GET","capability":"giving.read","scoped":false,"summary":"Gifts, newest first, with the campaign each belongs to.","detail":"Donor identity is `giving.read` and never member-wide, so this is behind a capability most keys will not hold — and a gift the donor marked anonymous names nobody.","app":"giving","path":"/org-api/orgs/{orgId}/gifts"},{"name":"get_rigcheck_setup","method":"GET","capability":"rigcheck.manage","scoped":false,"summary":"What Rig Check holds here: every vehicle with its checklist, the reusable area library, the shift labels, when each rig was last walked and how many flags it left open.","detail":"Call this first. An organization with four rigs and a check walked this morning wants `list_flags`, not a programme it would be told was already there. It also names the two acts no key may perform, so you can say so rather than discover it.","app":"rigcheck","path":"/org-api/orgs/{orgId}/agent/rigcheck"},{"name":"setup_rigcheck_program","method":"POST","capability":"rigcheck.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Build a rig-check programme in one transaction: the reusable area library with its items, the fleet, and each vehicle’s checklist composed out of those areas.","detail":"Send `areas[{name,items[]}]`, `vehicles[{name,kind,checklist:[\"Cab\",\"O2 Cabinet\"]}]`, and optionally `shifts[]` and `notifyGroup`. **The document names things rather than carrying ids**: a checklist names its areas, resolved after this document’s own areas exist, so one call can describe a fleet from nothing. An item is a `toggle` (the normal line — untouched records OK at submit), a `choice` (options, each marked flagged or not), an `expiry` (a YYYY-MM month, flagged once past) or a `note`; `critical: true` is an out-of-service line. **Nothing is ever edited or removed.** An area already in the library keeps its items exactly as they are and gains the ones it lacks; a vehicle keeps its kind and its walk order; a retired rig is left retired. Each mismatch comes back in `warnings`, because an agent re-typing the checklist a crew has walked nightly for two years is worse than an honest mismatch. `dryRun` defaults to TRUE. A configured programme is not a checked rig: a person walks the first one.","app":"rigcheck","path":"/org-api/orgs/{orgId}/agent/rigcheck/setup"},{"name":"list_flags","method":"GET","capability":"rigcheck.manage","scoped":false,"summary":"What is broken on the fleet: every unresolved flag from each active vehicle’s latest check, with how many consecutive checks it has been open, since when, and whether the item is an out-of-service one.","detail":"The six-in-the-morning read. Only the LATEST check of each rig counts, which is what the flag board shows a person — a flag that this morning’s check came back clean on is history, not a defect. `checksOpen` is the fact worth relaying: \"missing\" is a note, and \"missing on the last nine checks, since 2 October\" is a reason to act today. Vehicles nobody has ever walked are counted in `totals.neverChecked`. You cannot close any of these: say who can.","app":"rigcheck","path":"/org-api/orgs/{orgId}/agent/rigcheck/flags"},{"name":"get_vehicle_history","method":"GET","capability":"rigcheck.manage","scoped":false,"summary":"One rig’s record: its submitted checks newest first with who walked each and what it flagged, the items that keep coming back, and what is expiring.","detail":"Vehicle ids come from `get_rigcheck_setup`. `limit` is 1–100 and defaults to 30. `recurring` is the pattern a single check cannot show — an item flagged in more than one of these walks, counted; `expiring` is every expiry item from the latest check that is past or runs out inside ninety days, which is the question a chief asks once a month and nobody enjoys. No answers travel line by line: a check read back as paper is a screen a person uses.","app":"rigcheck","path":"/org-api/orgs/{orgId}/agent/rigcheck/vehicles/{vehicleId}/history"},{"name":"list_groups","method":"GET","capability":null,"scoped":false,"summary":"The organization’s groups — committees, crews and boards — with who sits in each.","app":"members","path":"/org-api/orgs/{orgId}/groups"},{"name":"list_people","method":"GET","capability":"people.read","scoped":true,"summary":"The roster. Fields marked sensitive are omitted without people.sensitive.read.","app":"members","path":"/org-api/orgs/{orgId}/people"},{"name":"get_person","method":"GET","capability":"people.read","scoped":true,"summary":"One person’s record, their groups and their standing.","app":"members","path":"/org-api/orgs/{orgId}/people/{personId}"},{"name":"add_person","method":"POST","capability":"people.write","scoped":true,"writes":true,"summary":"Add a person to the roster. Does not invite them or email them.","app":"members","path":"/org-api/orgs/{orgId}/people"},{"name":"send_message","method":"POST","capability":"messages.send","scoped":true,"writes":true,"confirmation":"message","summary":"Email the organization, a group, or named people.","detail":"Needs a person to confirm it, every time (R51). Send the request, get a 403 with `confirmation_required` and a sentence saying what will go out, ask the person to approve it in the Agents app, then send the IDENTICAL request again. Changing a single character means asking again, because approval binds to the exact message.","app":"members","path":"/org-api/orgs/{orgId}/messages"},{"name":"delete_person","method":"DELETE","capability":"people.write","scoped":true,"writes":true,"confirmation":"delete","summary":"Remove a person from the organization.","detail":"Needs a person to confirm it, every time (R51), and the confirmation names who is being removed. Ends their membership and their group seats; it is not a purge — their history stays, because an audit log with holes in it is not an audit log.","app":"members","path":"/org-api/orgs/{orgId}/people/{personId}"},{"name":"draft_election","method":"POST","app":"vote","capability":"vote.manage","scoped":false,"writes":true,"dryRun":true,"summary":"Write a DRAFT election — the questions and the whole slate of candidates — for a person to review and open.","detail":"Send {title, description?, closesAt?, questions:[{title, description?, maxChoices, options:[{name, blurb?}]}]}. `dryRun` defaults to TRUE and returns the plan having written nothing. It lands as a draft: no member can see it and nobody can vote until a PERSON opens it in Vote, which no key may do. This exists because a twelve-candidate slate with blurbs is the only clerical part of running an election.","path":"/vote-api/agent/orgs/{orgId}/elections/draft"},{"name":"get_election_status","method":"GET","app":"vote","capability":"vote.manage","scoped":false,"summary":"Where each election stands — draft, open or closed — and how many have voted, as two numbers.","detail":"Turnout is `{voted, eligible}` and can never be anything else: nothing in Vote joins a ballot to a voter, so there is no request that correlates who voted with how. Drafts are included, because a draft nobody has opened is the most useful thing this answers.","path":"/vote-api/agent/orgs/{orgId}/elections"},{"name":"get_results","method":"GET","app":"vote","capability":"vote.manage","scoped":false,"summary":"The count for an election that has CLOSED, with option names beside the votes.","detail":"A closed election only. An open one answers 409 `not_closed` with its status rather than an empty result, because \"no results\" and \"not yet\" are different facts and reporting the first for the second would be wrong in front of somebody waiting on an outcome. Nothing tallies while voting is live, for agents or admins.","path":"/vote-api/agent/orgs/{orgId}/elections/{electionId}/results"},{"name":"get_manifest","method":"GET","capability":null,"scoped":true,"summary":"This document, scoped to one organization: which tools this key may call, and who it acts as.","detail":"Call it first. It is also the whoami: it names the person the key delegates from, the capabilities it effectively holds, and when it expires.","app":"agents","path":"/org-api/orgs/{orgId}/agent/manifest"}],"humanOnly":[{"name":"mint_agent_key","method":"POST","reason":"A key that could mint a key could mint a non-expiring copy of itself, and revoking the original would stop meaning anything.","path":"/org-api/orgs/{orgId}/agent-keys"},{"name":"found_org","method":"POST","reason":"Founding an organization means owning its data and its bill, and the founder becomes its Owner. Ask the person to create it, then hand its work to an agent.","path":"/org-api/orgs"},{"name":"join_org","method":"POST","reason":"Taking up membership is a person’s own act. An agent cannot give its person standing.","path":"/org-api/orgs/join"},{"name":"redeem_invite","method":"POST","reason":"Accepting a role somebody offered you is the same kind of act as joining.","path":"/org-api/orgs/redeem-invite"},{"name":"record_motion_result","method":"POST","reason":"This records what a board DID decide, not what it should. An agent may draft the agenda and the minutes; the decision is the room’s.","path":"/org-api/orgs/{orgId}/meetings/{meetingId}/motions"},{"name":"approve_minutes","method":"POST","reason":"Approved minutes are the organization’s record of its own decisions. An agent that could approve them could write history.","path":"/org-api/orgs/{orgId}/meetings/{meetingId}/minutes/approve"},{"name":"clock_in","method":"POST","reason":"Clocking in is a person standing at a station. An agent that can start the clock can forge a timesheet, which is money and a legal record.","path":"/org-api/orgs/{orgId}/shifts/time/clock-in"},{"name":"clock_out","method":"POST","reason":"The other half of the same claim.","path":"/org-api/orgs/{orgId}/shifts/time/clock-out"},{"name":"write_timesheet_entry","method":"POST","reason":"A manual entry is the same claim typed instead of punched. An agent may read a timesheet and never write one — editing and deleting entries are refused for the same reason.","path":"/org-api/orgs/{orgId}/shifts/time/entries"},{"name":"edit_timesheet_entry","method":"PATCH","reason":"Hours are a person’s own word about where they were.","path":"/org-api/orgs/{orgId}/shifts/time/entries/{id}"},{"name":"delete_timesheet_entry","method":"DELETE","reason":"Hours are a person’s own word about where they were.","path":"/org-api/orgs/{orgId}/shifts/time/entries/{id}"},{"name":"connect_paypal","method":"PUT","reason":"The organization’s own PayPal credential. Handing that to an agent would not delegate the treasurer’s work but their bank access — and unlike every other power here it would outlive revocation, because the credential would have been copied. A person connects it in Giving → Settings.","path":"/org-api/orgs/{orgId}/giving/payments"},{"name":"record_gift","method":"POST","reason":"Writing down that money arrived is a statement about the world somebody has to stand behind: the check was in the envelope, the cash was counted. An agent may set the campaign up and read what came in; the treasurer says what was banked. A gift that arrives through the public giving page is recorded by the payment itself, which is a fact rather than a claim.","path":"/org-api/orgs/{orgId}/gifts"}],"withheld":[{"name":"charge_or_refund","nature":"pending","reason":"Moving money needs a human confirmation (R51), and unlike email and deletion it has no agent-facing route yet: the money paths run through Stripe and a person. Ask the person to do it."},{"name":"perform_rig_check","nature":"never","capability":"rigcheck.perform","reason":"Walking a vehicle check attests that a human looked at the truck. The capability itself cannot be delegated (R55), so no key will ever carry it. What an agent may do instead is next to it: `setup_rigcheck_program` builds the programme, and `list_flags` reads what the fleet left open."},{"name":"resolve_rig_flag","nature":"never","capability":"rigcheck.resolve","reason":"Closing a defect says somebody dealt with it, which is the same species of claim. Also undelegatable (R55). Report the flag to somebody who can: `list_flags` says how long it has been open, which is the part that gets it done."},{"name":"cast_ballot","nature":"never","reason":"Voting is a property of membership and never a machine’s act; an agent may not cast, see or correlate a ballot. Vote is a separate service with no agent door at all, so there is nothing to call."},{"name":"open_or_close_voting","nature":"never","reason":"Opening voting decides when the organization starts deciding, and closing it decides when people stop being heard. An agent may draft an election; a person runs it."}]}