{"openapi":"3.1.0","info":{"title":"Futurecast Agent API","version":"1.0.0","description":"The Futurecast agent API lets any agent read a user's futures and boards and queue changes on their behalf, with the user's approval.\n\nFuturecast is a manifestation practice app. Users write futures: short first-person, present-tense statements of a goal as if it is already true. Boards collect futures into themed practices, and the Daily Ritual is the user's curated daily session.\n\nTwo things make this API different from a typical CRUD API:\n\n1. Reads come from a mirrored catalog. The user's phone is the source of truth and syncs a private mirror to this API. Reads can be partial while the mirror is seeding; every read response says so in catalog_state. Boards the user marked private, and every future on them, are simply absent, never marked.\n\n2. Writes are queued, not instant. Write endpoints validate, persist a command, and return immediately with \"status\": \"queued\". The change applies the next time the user opens Futurecast on their phone. Creation responses include the id the entity will have once applied, so you can chain writes freely without waiting. Poll GET /v1/agent/commands/{command_id} for the outcome. Commands expire after 72 hours if the app never opens.\n\nAuthentication is OAuth 2.1 with PKCE (S256 only). Clients are public: register at POST /register (dynamic registration), then run the authorization-code flow at /authorize. The user approves the connection inside the Futurecast app with a pairing code and chooses the scopes you get. Loopback redirect URIs (http://127.0.0.1 or http://localhost, any port) are accepted for native and CLI clients. Tokens only work while the user's grant stays active; the user can revoke it in the app at any time.\n\nThere is no per-minute rate limit, but at most 50 commands may be waiting for the phone at once; further writes fail with too_many_queued until pending commands apply or expire."},"servers":[{"url":"https://api.futurecastapp.com"}],"security":[{"oauth":[]}],"paths":{"/v1/agent/status":{"get":{"operationId":"getStatus","summary":"Check the connection and catalog sync state","description":"Check whether the account is connected and whether the mirrored catalog is empty, seeding, or ready.","security":[{"oauth":["read"]}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentStatus"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/futures":{"get":{"operationId":"listFutures","summary":"List futures (query: matching, limit, board_id)","description":"List the user's futures from the mirrored catalog. Use matching for a case-insensitive search across goal and affirmation text, and board_id to list futures on one board.","security":[{"oauth":["read"]}],"parameters":[{"name":"matching","in":"query","required":false,"schema":{"type":"string"},"description":"Case-insensitive substring match on the future's goal and affirmation text."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Maximum results to return (default 50, cap 200)."},{"name":"board_id","in":"query","required":false,"schema":{"type":"string"},"description":"Only return futures that are on this board."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"catalog_state":{"$ref":"#/components/schemas/CatalogState"},"futures":{"type":"array","items":{"$ref":"#/components/schemas/Future"}}},"required":["catalog_state","futures"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createFuture","summary":"Create a future","description":"Create a future. Affirmation is required; goal is optional if the affirmation already captures it. You can pass board_id to add the new future to a board in the same queued command.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"affirmation":{"type":"string"},"goal":{"type":"string"},"board_id":{"type":"string"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["affirmation"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/futures/{id}":{"get":{"operationId":"getFuture","summary":"Get one future","description":"Get one future from the mirrored catalog.","security":[{"oauth":["read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Future id.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"catalog_state":{"$ref":"#/components/schemas/CatalogState"},"future":{"$ref":"#/components/schemas/Future"}},"required":["catalog_state","future"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateFuture","summary":"Update a future's text or goal","description":"Update a future's affirmation or goal text. Pass only the fields you want to change.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Future id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"affirmation":{"type":"string"},"goal":{"type":"string"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteFuture","summary":"Move a future to Recently Deleted","description":"Move a future to Recently Deleted. The user can restore it in the app for 30 days.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Future id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards":{"get":{"operationId":"listBoards","summary":"List boards (query: matching, limit)","description":"List the user's boards from the mirrored catalog. Use matching for a case-insensitive search across board title and intention.","security":[{"oauth":["read"]}],"parameters":[{"name":"matching","in":"query","required":false,"schema":{"type":"string"},"description":"Case-insensitive substring match on board title and intention."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200},"description":"Maximum results to return (default 50, cap 200)."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"catalog_state":{"$ref":"#/components/schemas/CatalogState"},"boards":{"type":"array","items":{"$ref":"#/components/schemas/Board"}}},"required":["catalog_state","boards"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"createBoard","summary":"Create a board","description":"Create a board. Name is required; intention is optional and should describe the practice the board is meant to support.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"intention":{"type":"string"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["name"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards/{id}":{"get":{"operationId":"getBoard","summary":"Get one board with its futures inline","description":"Get one board from the mirrored catalog with its future ids and readable futures inline.","security":[{"oauth":["read"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"catalog_state":{"$ref":"#/components/schemas/CatalogState"},"board":{"$ref":"#/components/schemas/BoardDetail"}},"required":["catalog_state","board"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"updateBoard","summary":"Rename a board or rewrite its intention","description":"Rename a board or rewrite its intention. Pass null for intention to clear it.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"intention":{"type":["string","null"],"description":"Board intention text; pass null to clear the intention."},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"deleteBoard","summary":"Move a board to Recently Deleted","description":"Move a board to Recently Deleted. By default this deletes the board only, not its futures; pass include_futures to also move its futures to Recently Deleted.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"include_futures":{"type":"boolean"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards/{board_id}/futures":{"post":{"operationId":"addToBoard","summary":"Add a future to a board","description":"Add a future to a board. For multiple changes, use `POST /v1/agent/memberships/edit` instead.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"future_id":{"type":"string"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["future_id"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards/{board_id}/futures/{future_id}":{"delete":{"operationId":"removeFromBoard","summary":"Remove a future from a board","description":"Remove a future from a board. For multiple changes, use `POST /v1/agent/memberships/edit` instead.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}},{"name":"future_id","in":"path","required":true,"description":"Future id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/memberships/edit":{"post":{"operationId":"editMemberships","summary":"Bulk add/remove futures on boards (up to 100 changes)","description":"Add or remove futures on boards in bulk. One command carries up to 100 changes and the app applies them in a single pass, reporting a per-change outcome. Prefer this over repeated add_to_board or remove_from_board calls whenever you are making more than a couple of changes.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"add":{"type":"array","items":{"type":"object","properties":{"future_id":{"type":"string"},"board_ids":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["future_id","board_ids"],"additionalProperties":false}},"remove":{"type":"array","items":{"type":"object","properties":{"future_id":{"type":"string"},"board_ids":{"type":"array","items":{"type":"string"},"minItems":1}},"required":["future_id","board_ids"],"additionalProperties":false}},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/ritual-membership":{"post":{"operationId":"setRitualMembership","summary":"Add or remove a future or board from the Daily Ritual","description":"Add or remove a future from the user's Daily Ritual, or add or remove a whole board's futures at once. The Daily Ritual is the user's curated daily practice. Keep it focused: a tight ritual beats a crowded one. Adding a board adds its active futures individually; the board itself is not stored in the ritual.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add","remove"]},"future_id":{"type":"string"},"board_id":{"type":"string"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["action"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards/{board_id}/pinned":{"put":{"operationId":"setBoardPinned","summary":"Pin or unpin a board on Home","description":"Pin or unpin a board on the user's Home screen. Pinned boards appear front and center as Practice Next. Pinning a board you just created for the user is a natural follow-up.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pinned":{"type":"boolean"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["pinned"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"setBoardPinnedPatch","summary":"Pin or unpin a board on Home","description":"Pin or unpin a board on the user's Home screen. Pinned boards appear front and center as Practice Next. Pinning a board you just created for the user is a natural follow-up.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"pinned":{"type":"boolean"},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["pinned"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/boards/{board_id}/reminder":{"put":{"operationId":"setBoardReminder","summary":"Create or replace a board's practice reminder","description":"Create or update the practice reminder for a board. One reminder per board: calling this again replaces its time, days, and delivery. Delivery is a standard notification by default; pass delivery alarm for a full alarm, which needs the user's Alarm access. Without that access the reminder saves as a notification and the result explains why. Reminders skip automatically on days the user already practiced that board. iOS limits scheduled notifications, so with many reminders some deliver as a brief nudge instead of the rich preview. Times are 24-hour HH:MM in the user's local time.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"weekdays":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"show_on_lock_screen":{"type":"boolean"},"delivery":{"type":"string","enum":["notification","alarm"]},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["time"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"operationId":"setBoardReminderPatch","summary":"Create or replace a board's practice reminder","description":"Create or update the practice reminder for a board. One reminder per board: calling this again replaces its time, days, and delivery. Delivery is a standard notification by default; pass delivery alarm for a full alarm, which needs the user's Alarm access. Without that access the reminder saves as a notification and the result explains why. Reminders skip automatically on days the user already practiced that board. iOS limits scheduled notifications, so with many reminders some deliver as a brief nudge instead of the rich preview. Times are 24-hour HH:MM in the user's local time.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"time":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"weekdays":{"type":"array","items":{"type":"string","enum":["mon","tue","wed","thu","fri","sat","sun"]}},"show_on_lock_screen":{"type":"boolean"},"delivery":{"type":"string","enum":["notification","alarm"]},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"required":["time"],"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"operationId":"removeBoardReminder","summary":"Remove a board's practice reminder","description":"Remove a board's practice reminder.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write"]}],"parameters":[{"name":"board_id","in":"path","required":true,"description":"Board id.","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueuedCommand"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/commands/status":{"post":{"operationId":"getCommandStatuses","summary":"Check up to 25 queued commands","description":"Check what happened to queued write commands. Returns queued (not yet applied), applied (with the result, including ids of newly created items), failed (with the reason), or expired (never applied before the 72-hour window closed). Unknown ids return status not_found. Pass command_id or up to 25 command_ids.","security":[{"oauth":["read"]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"command_id":{"type":"string"},"command_ids":{"type":"array","items":{"type":"string"},"minItems":1,"maxItems":25}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commands":{"type":"array","items":{"$ref":"#/components/schemas/CommandStatus"}}},"required":["commands"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/commands/{command_id}":{"get":{"operationId":"getCommand","summary":"Check one queued command","description":"Check what happened to a queued write command. Returns queued (not yet applied), applied (with the result, including ids of newly created items), failed (with the reason), or expired (never applied before the 72-hour window closed). An unknown id returns status not_found.","security":[{"oauth":["read"]}],"parameters":[{"name":"command_id","in":"path","required":true,"description":"Command id.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"commands":{"type":"array","items":{"$ref":"#/components/schemas/CommandStatus"}}},"required":["commands"],"additionalProperties":false}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Catalog seeding","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/agent/visions/generate":{"post":{"operationId":"generateVision","summary":"Queue an AI vision image for a future","description":"Queue an AI vision image for a future. The user's device generates the image the next time they open Futurecast, using their reference photos by default so the vision can carry their likeness. Target an existing future with future_id, or pass create to make a new future and visualize it in one step (create takes the same fields as create_future, including board_id). Prefer create whenever the future does not exist yet: future_id only works for futures already readable in the catalog, so chaining create_future into a vision attach forces a wait for the user to open the app. Requires both the write and visions permissions. A credit is only spent when the image lands.\n\nQueued write: returns immediately with \"status\": \"queued\" and a command_id. The change applies the next time the user opens Futurecast on their phone. Poll GET /v1/agent/commands/{command_id} for the outcome. Replaying the same idempotency_key returns the original command instead of enqueuing a duplicate.","security":[{"oauth":["write","visions"]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"future_id":{"type":"string"},"create":{"type":"object","properties":{"affirmation":{"type":"string"},"goal":{"type":"string"},"board_id":{"type":"string"}},"required":["affirmation"],"additionalProperties":false},"custom_prompt":{"type":"string","minLength":1,"maxLength":2000,"description":"Write the full image prompt yourself when the user wants a specific image, usually one without their likeness. When set, the user's reference photos are not used and your prompt is generated as written. Describe one complete scene in concrete visual detail: subject, setting, light, mood, camera feel. Futurecast visions are candid photorealistic photographs in 9:16 portrait, so write for that look unless the user asks for a different style. Leave unset for the normal pipeline that generates the vision with the user's likeness from their reference photos."},"idempotency_key":{"type":"string","maxLength":128,"description":"Optional idempotency key; replaying it returns the original command."}},"additionalProperties":false}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateVisionResult"}}}},"400":{"description":"Validation error, or too_many_queued when 50 commands are already waiting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or invalid token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Missing a required scope; `required_scope` names it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"oauth":{"type":"oauth2","description":"OAuth 2.1 with S256 PKCE is required. Clients are public and do not use a secret. Dynamic registration lives at `/register`. Loopback redirect URIs with any port are accepted for native and CLI clients.","flows":{"authorizationCode":{"authorizationUrl":"https://api.futurecastapp.com/authorize","tokenUrl":"https://api.futurecastapp.com/token","refreshUrl":"https://api.futurecastapp.com/token","scopes":{"read":"Read the mirrored catalog: futures, boards, status.","write":"Queue changes: create, update, delete, organize.","visions":"Queue AI vision images (also requires write)."}}}}},"schemas":{"CatalogState":{"oneOf":[{"type":"string","const":"empty"},{"type":"string","const":"ready"},{"type":"object","properties":{"state":{"type":"string","const":"seeding"},"synced":{"type":"integer"},"total":{"type":"integer"}},"required":["state","synced","total"],"additionalProperties":false}]},"Future":{"type":"object","properties":{"id":{"type":"string"},"goal_text":{"type":"string"},"affirmation_text":{"type":"string"},"shader_variant":{"type":["string","null"]},"in_ritual":{"type":"boolean"}},"required":["id","goal_text","affirmation_text","shader_variant","in_ritual"],"additionalProperties":false},"Board":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"intention":{"type":["string","null"]},"pinned":{"type":"boolean"}},"required":["id","title","intention","pinned"],"additionalProperties":false},"BoardDetail":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"intention":{"type":["string","null"]},"pinned":{"type":"boolean"},"future_ids":{"type":"array","items":{"type":"string"}},"futures":{"type":"array","items":{"$ref":"#/components/schemas/Future"}},"futures_truncated":{"type":"boolean"}},"required":["id","title","intention","pinned","future_ids","futures","futures_truncated"],"additionalProperties":false},"AgentStatus":{"type":"object","properties":{"account_connected":{"type":"boolean","const":true},"catalog_state":{"$ref":"#/components/schemas/CatalogState"},"commands_pending":{"type":"integer"}},"required":["account_connected","catalog_state","commands_pending"],"additionalProperties":false},"QueuedCommand":{"oneOf":[{"type":"object","properties":{"status":{"type":"string"},"command_id":{"type":"string"},"future_id":{"type":"string"}},"required":["status","command_id","future_id"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string"},"command_id":{"type":"string"},"board_id":{"type":"string"}},"required":["status","command_id","board_id"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string"},"command_id":{"type":"string"},"pair_count":{"type":"integer"}},"required":["status","command_id","pair_count"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"applied"},"command_id":{"type":"string"},"result":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]}},"required":["status","command_id","result"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","enum":["failed","expired"]},"command_id":{"type":"string"},"reason":{"type":"string"}},"required":["status","command_id","reason"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string"},"command_id":{"type":"string"}},"required":["status","command_id"],"additionalProperties":false}]},"GenerateVisionResult":{"oneOf":[{"type":"object","properties":{"status":{"type":"string","const":"queued"},"command_id":{"type":"string"},"future_id":{"type":"string"},"visions_balance":{"type":"integer"}},"required":["status","command_id","future_id"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"applied"},"command_id":{"type":"string"},"future_id":{"type":"string"},"result":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]}},"required":["status","command_id","future_id","result"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"failed"},"command_id":{"type":"string"},"future_id":{"type":"string"},"reason":{"type":"string"},"refunded":{"type":"boolean"},"charged":{"type":"boolean"}},"required":["status","command_id","future_id","reason"],"additionalProperties":false},{"type":"object","properties":{"status":{"type":"string","const":"expired"},"command_id":{"type":"string"},"future_id":{"type":"string"},"refunded":{"type":"boolean"},"charged":{"type":"boolean"}},"required":["status","command_id","future_id"],"additionalProperties":false}]},"CommandStatus":{"type":"object","properties":{"command_id":{"type":"string"},"status":{"type":"string","enum":["queued","applied","failed","expired","not_found"]},"result":{"oneOf":[{"type":"object","additionalProperties":true},{"type":"null"}]},"reason":{"type":["string","null"]},"custom_prompt":{"type":"boolean"}},"required":["command_id","status","result","reason"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"string"},"required_scope":{"type":"string"}},"required":["error"],"additionalProperties":false}}}}