Tools reference

Every MCP tool maps to one or two REST calls. Reads return an envelope with coverage; writes take idempotency_key and reason and end as executed or pending_approval. Generated from apps/mcp and KONTRAKT §11 (v1.0.0).

Read tools

#get_business_summary read

Financial summary of one business: revenue, costs (planned/reserved/actual kept separate), by category. Defaults to the current month.

get_business_summary(business_id, from?, to?) → FinancialSummary {revenue, costs, by_category[], planned/reserved/actual}
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_business_context read

START HERE. One compact snapshot of a business (30d summary, free capital, forecast status, ads, top subscriptions, reservations, pending approvals, your passport, coverage). Replaces 6+ separate calls.

get_business_context(business_id) → {business, projects[], summary_30d, free_capital, forecast_status, subscriptions_top[], active_reservations[], pending_approvals[], my_passport,
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_available_capital read

Free capital = cash_known − committed_costs − reserved_budgets − upcoming_subscriptions over the horizon. Check before proposing any spend.

get_available_capital(Scope, horizon_days?=30) → FreeCapital (§6)
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_subscriptions read

Subscriptions in scope with cost allocations between businesses and capacity.

get_subscriptions(Scope, status?) → Subscription[] (z alokacjami)
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_subscription_capacity read

Seats/slots of shared subscriptions: total, used, free, utilization, usage per workspace. Check for free capacity before proposing a new tool.

get_subscription_capacity(subscription_id? | Scope) → {subscription_id, unit, total, used, free, utilization, by_workspace[]}[]
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_ad_performance readcoming soon

Ad spend per platform: planned / reserved / actual, results and budget pacing.

Ads integrations (Google Ads, Meta Ads) are coming soon: the tool works against planned budgets and reservations; live spend arrives once an Ads integration is connected.

get_ad_performance(business_id, from?, to?, platform?) → {platform, spend: P/R/A, results?, pacing?}[]
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_cashflow_forecast read

Cash-flow forecast (7/30/90 days or custom range) with status list and reasons. Estimated components are labelled, never treat them as real data. Daily points are omitted unless include_points=true.

get_cashflow_forecast(Scope, horizon: 7|30|90 | {from,to}) → Forecast (§7)
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_project_health read

Health of a business and its projects as a list of statuses with reasons (no scores).

get_project_health(business_id, project_id?) → {status[], reasons[]} (bez score)
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_daily_brief read

Morning brief: last-24h highlights, alerts, upcoming costs (7d), approvals pending. Pass business_id unless you hold an `all` grant.

get_daily_brief(date?, business_id?) → {date, business_id|null, highlights[], alerts[], upcoming[], approvals_pending, goal_deviations[], agent_costs_30d: {agent_id, name, total_minor, currency}[] /* 0.7.1 */}
Required scope
finances (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_goals read

Goals in scope (business/project/personal + your own agent goals) with `progress` computed from the ledger: current_value, target, gap, on_track, data_class. Analyse against the goal, not just whether revenue grows.

get_goals(Scope, include_archived?=false) → (Goal & {progress})[]   (0.5.2, §2.17; tylko cele w zasięgu grantów)
Required scope
goals (observe; onboarding = własny passport, zawsze dozwolone)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

#get_decision_outcomes read

Decision journal review: for each recorded decision, actual vs expected after the horizon (variance, met) and before/after change of ad_spend, revenue, net, roas, all with data_class. Use it to learn from your own history.

get_decision_outcomes(Scope, since?, limit?=20) → DecisionOutcome[]        (0.5.2, §2.18; własne decyzje agenta)
Required scope
decisions (observe)
Result
envelope { data, coverage, fx?, provenance_summary?, request_id }

Write tools

Common parameters W = { idempotency_key, reason }; Scope = { business_id?, project_id?, personal? }; Money = { amount_minor, currency }.

#create_expense write

Record an ACTUAL one-off expense. Link reservation_id / fulfills_event_id when it consumes a reservation or fulfils a planned expense.

create_expense(W, Scope, Money, occurred_on, category, description) → FinancialEvent
Required scope
expenses
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#create_recurring_expense write

Create a subscription (recurring cost). Use either a single Scope or allocations[] (share_bps sum = 10000) for shared costs.

create_recurring_expense(W, Scope|allocations[], name, vendor, Money, billing_interval, next_charge_on, plan?, capacity?[]) → Subscription
Required scope
subscriptions + expenses
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#create_planned_expense write

Record a PLANNED one-off expense (intent). It enters forecasts and committed costs; it is never overwritten by the actual.

create_planned_expense(W, Scope, Money, expected_on, category, description) → FinancialEvent
Required scope
planned_expenses
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#reserve_budget write

Block capital for a purpose and period (BudgetReservation). Reduces free capital until period_end or release.

reserve_budget(W, Scope, Money, purpose, category, period_start, period_end) → BudgetReservation
Required scope
budget_reserve
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#allocate_shared_expense write

Replace the cost split of a shared subscription. Full list, share_bps must sum to 10000.

allocate_shared_expense(W, subscription_id, allocations: {Scope, method, share_bps}[]) → SubscriptionAllocation[]
Required scope
allocations
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#propose_ad_budget_change writecoming soon

Propose changing a DAILY ad budget. Always leaves an Approval record; executed at once only with ads:execute and within max_change_pct_per_day. The result carries `simulation` (impact on forecast/runway/free capital) so you and the owner see the effect before it lands. MVP writes the plan only, no ad platform is touched.

Ads integrations (Google Ads, Meta Ads) are coming soon: the tool works against planned budgets and reservations; live spend arrives once an Ads integration is connected.

propose_ad_budget_change(W, business_id, platform, campaign_id?, current: Money, proposed: Money) → Approval (zawsze; execute w MVP = zapis planu, bez wywołań platform)
Required scope
ads (≥recommend)
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#propose_goal write

Propose a goal (business goal or your own agent constraint, e.g. max ads spend). Always ends as pending_approval, the owner decides. Money targets in minor units.

propose_goal(W, Scope, metric, comparator, target_value, currency?, title?, deadline?, agent_id?) → Approval (zawsze 202, action=create_goal; §2.17)
Required scope
goals (≥ recommend, wynik zawsze Approval)
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#request_permission write

Ask the owner for a missing grant (scope × level × resource). Always ends as pending_approval; explain in `reason` what analysis is blocked without it.

request_permission(W, scope, level, resource, limits?, expires_at?) → Approval
Required scope
-
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

#create_decision write

Decision journal: record what you are about to do, why, the expected impact (expected[] = measurable targets with horizon) and the data behind it, before a significant action. Reviewed against actuals after the horizon via get_decision_outcomes.

create_decision(W, Scope, title, rationale, expected_impact?, expected?[], action_ref?, refs?[]) → FinancialEvent(agent_action) (§2.18)
Required scope
decisions
Result
201 executed · 202 pending_approval (level recommend or per-action limit exceeded)

All signatures

// READ
get_business_summary(business_id, from?, to?)            → FinancialSummary {revenue, costs, by_category[], planned/reserved/actual}
get_business_context(business_id)                        → {business, projects[], summary_30d, free_capital, forecast_status, subscriptions_top[], active_reservations[], pending_approvals[], my_passport,
                                                              recent_decisions: {id, title, decided_on, review: {status, met}, expected[], observed[]}[] /* 0.7.0, ≤5 */,
                                                              agent_costs_30d: {agent_id, name, total_minor, currency}[] /* 0.7.1 */, other_agents_activity: {agent_id, name, action, at, outcome}[] /* 0.7.1: 24 h, max 10, spec §31 */,
                                                              anomalies: {code: "revenue_drop"|"cost_spike"|"no_revenue_14d", message, amount_minor?, data_class}[] /* 0.7.0: revenue 7d vs poprzednie 7d < -30 %; koszt dnia > 3× mediana 30 d w kategorii; brak przychodu 14 dni */ }
get_available_capital(Scope, horizon_days?=30)           → FreeCapital (§6)
get_subscriptions(Scope, status?)                        → Subscription[] (z alokacjami)
get_subscription_capacity(subscription_id? | Scope)      → {subscription_id, unit, total, used, free, utilization, by_workspace[]}[]
get_ad_performance(business_id, from?, to?, platform?)   → {platform, spend: P/R/A, results?, pacing?}[]
get_cashflow_forecast(Scope, horizon: 7|30|90 | {from,to}) → Forecast (§7)
get_project_health(business_id, project_id?)             → {status[], reasons[]} (bez score)
get_daily_brief(date?, business_id?)                     → {date, business_id|null, highlights[], alerts[], upcoming[], approvals_pending, goal_deviations[], agent_costs_30d: {agent_id, name, total_minor, currency}[] /* 0.7.1 */}
get_goals(Scope, include_archived?=false)                → (Goal & {progress})[]   (0.5.2, §2.17; tylko cele w zasięgu grantów)
get_decision_outcomes(Scope, since?, limit?=20)          → DecisionOutcome[]        (0.5.2, §2.18; własne decyzje agenta)
get_onboarding(resource?)                                → Onboarding (0.5.2, spec §21; 0.6.1: `resource` = `business:<id>` | `project:<id>` zawęża sekcje do jednego zasobu) + MCP resource `operlance://onboarding` (markdown ≤ 2 000 tokenów, bez sekretów):
  Onboarding = { about: string, my_passport: { agent: {id, name, kind}, grants: string[] /* "ads:execute@business:<id> ≤ 500 PLN/akcja, 10%/dzień" */, expires_at, token_expires_at },
    businesses_accessible: {id, name, currency}[], forbidden: string[], limits: PassportGrant["limits"][], goals: Goal[],
    concepts: { planned_vs_actual, reservations, decision_journal, approvals }: Record<string,string>, examples: {title, tools: string[]}[] }

// WRITE (W wymagane)
create_expense(W, Scope, Money, occurred_on, category, description)                         → FinancialEvent
create_recurring_expense(W, Scope|allocations[], name, vendor, Money, billing_interval, next_charge_on, plan?, capacity?[]) → Subscription
create_planned_expense(W, Scope, Money, expected_on, category, description)                → FinancialEvent
reserve_budget(W, Scope, Money, purpose, category, period_start, period_end)               → BudgetReservation
allocate_shared_expense(W, subscription_id, allocations: {Scope, method, share_bps}[])     → SubscriptionAllocation[]
propose_ad_budget_change(W, business_id, platform, campaign_id?, current: Money, proposed: Money) → Approval (zawsze; execute w MVP = zapis planu, bez wywołań platform)
request_permission(W, scope, level, resource, limits?, expires_at?)                        → Approval
create_decision(W, Scope, title, rationale, expected_impact?, expected?[], action_ref?, refs?[]) → FinancialEvent(agent_action) (§2.18)
propose_goal(W, Scope, metric, comparator, target_value, currency?, title?, deadline?, agent_id?) → Approval (zawsze 202, action=create_goal; §2.17)

// 0.7.0 (spec §29 What-if, §25 Agent Requests, §28 review)
simulate_scenario(Scope, horizon_days?=7|30|90, changes: Change[1..20]) → Simulation        // READ (finances:observe + forecast:observe); bez zapisu, bez Idempotency-Key
  Change = { kind: "add_expense", amount: Money, on?, category?, label? } | { kind: "add_recurring", amount: Money, billing_interval, interval_count?=1, next_charge_on?, label? }
         | { kind: "cancel_subscription", subscription_id } | { kind: "change_ad_budget", platform, current: Money, proposed: Money /* budżety DZIENNE */, campaign_id?, from? } | { kind: "add_revenue", amount: Money, on?, label? }
  ForecastSnapshot = { closing_cash_minor, min_cash_minor, net_minor, projected_in_minor, projected_out_minor, runway_days|null, free_capital_minor, break_even_on|null, statuses[] }
  Simulation = { scope, currency, horizon_days, from, to, changes[], baseline: ForecastSnapshot, simulated: ForecastSnapshot,
                 delta: { closing_cash_minor, min_cash_minor, net_minor, runway_days|null, free_capital_minor, break_even_on: {from, to} },
                 unmodeled_minor /* część zmiany nieodwzorowana eventami, odjęta analitycznie */, data_class: "estimated", notes: string[] }
request_data(W, kind: "connect_integration"|"provide_data", details: { provider? /* connect */, field? /* provide, np. cash_snapshot */, why, impact_on_coverage? }) → Approval (202, action = kind)
get_decision_review(decision_id)                          → DecisionOutcome (§2.18; własne decyzje)
get_agent_economics(agent_id?, business_id?, from?, to?)  → AgentEconomics (0.7.1, spec §32; agent: tylko własne)

// 1.0.0-rc.2 (Agentowy 66), experimental
get_personal_summary(from?, to?)                          → FinancialSummary dla personal (scope `finances:observe@personal`; brak grantu → scope_denied z what_to_do „poproś o grant @personal")
get_personal_forecast(horizon?: 7|30|90 | {from,to}, include_points?) → Forecast dla personal (jak wyżej)
plan_capacity(needs: {unit, amount, business_id}[1..10], W?) → { per_need: { need, covered_by: {subscription_id, free}|null, shortfall: int, suggestion: "allocate"|"upgrade"|"buy_new", est_cost_minor: int|null, proposed_allocations[] }[], proposals: Approval[] /* tylko z W: Approval allocate_shared_expense, nic nie kupuje */ }
  AgentEconomics = { agent_id, period: {from,to}, currency, cost: { total_minor, runtime_minor /* agent_runtime z ledgera */, tools_minor /* alokacje subskrypcji z SubscriptionAllocation.agent_id */, by_category[] },
                     actions: { total, by_tool: Record<string,int>, denied: int }, approvals: { proposed, approved, rejected, expired, pending },
                     impact: { reviewed_decisions, met, missed, revenue_generated_minor|null, cost_saved_minor|null, data_class },
                     roi: { value: number|null, data_class: "estimated"|"unavailable", note } }
Source: KONTRAKT §11 (Polish, verbatim from the contract)

#11. MCP tools (spec §19), sygnatury

Serwer operlance, auth tokenem agenta. Każdy wynik: { data, coverage, provenance_summary?, request_id }. Błąd tool (0.6.1): isError: true + { code, message, what_to_do, retry_after_seconds?, details?, request_id } (code = kod z §9; what_to_do = jedno zdanie po angielsku, co agent ma zrobić, bez sekretów). Każdy write: parametry wspólne W = { idempotency_key: string, reason: string }; wynik { status: "executed"|"pending_approval", data?, approval_id? }. Błędy = kody z §9. Scope = { business_id?: string, project_id?: string, personal?: boolean }; Money = { amount_minor: int, currency: string }.

// READ
get_business_summary(business_id, from?, to?)            → FinancialSummary {revenue, costs, by_category[], planned/reserved/actual}
get_business_context(business_id)                        → {business, projects[], summary_30d, free_capital, forecast_status, subscriptions_top[], active_reservations[], pending_approvals[], my_passport,
                                                              recent_decisions: {id, title, decided_on, review: {status, met}, expected[], observed[]}[] /* 0.7.0, ≤5 */,
                                                              agent_costs_30d: {agent_id, name, total_minor, currency}[] /* 0.7.1 */, other_agents_activity: {agent_id, name, action, at, outcome}[] /* 0.7.1: 24 h, max 10, spec §31 */,
                                                              anomalies: {code: "revenue_drop"|"cost_spike"|"no_revenue_14d", message, amount_minor?, data_class}[] /* 0.7.0: revenue 7d vs poprzednie 7d < -30 %; koszt dnia > 3× mediana 30 d w kategorii; brak przychodu 14 dni */ }
get_available_capital(Scope, horizon_days?=30)           → FreeCapital (§6)
get_subscriptions(Scope, status?)                        → Subscription[] (z alokacjami)
get_subscription_capacity(subscription_id? | Scope)      → {subscription_id, unit, total, used, free, utilization, by_workspace[]}[]
get_ad_performance(business_id, from?, to?, platform?)   → {platform, spend: P/R/A, results?, pacing?}[]
get_cashflow_forecast(Scope, horizon: 7|30|90 | {from,to}) → Forecast (§7)
get_project_health(business_id, project_id?)             → {status[], reasons[]} (bez score)
get_daily_brief(date?, business_id?)                     → {date, business_id|null, highlights[], alerts[], upcoming[], approvals_pending, goal_deviations[], agent_costs_30d: {agent_id, name, total_minor, currency}[] /* 0.7.1 */}
get_goals(Scope, include_archived?=false)                → (Goal & {progress})[]   (0.5.2, §2.17; tylko cele w zasięgu grantów)
get_decision_outcomes(Scope, since?, limit?=20)          → DecisionOutcome[]        (0.5.2, §2.18; własne decyzje agenta)
get_onboarding(resource?)                                → Onboarding (0.5.2, spec §21; 0.6.1: `resource` = `business:<id>` | `project:<id>` zawęża sekcje do jednego zasobu) + MCP resource `operlance://onboarding` (markdown ≤ 2 000 tokenów, bez sekretów):
  Onboarding = { about: string, my_passport: { agent: {id, name, kind}, grants: string[] /* "ads:execute@business:<id> ≤ 500 PLN/akcja, 10%/dzień" */, expires_at, token_expires_at },
    businesses_accessible: {id, name, currency}[], forbidden: string[], limits: PassportGrant["limits"][], goals: Goal[],
    concepts: { planned_vs_actual, reservations, decision_journal, approvals }: Record<string,string>, examples: {title, tools: string[]}[] }

// WRITE (W wymagane)
create_expense(W, Scope, Money, occurred_on, category, description)                         → FinancialEvent
create_recurring_expense(W, Scope|allocations[], name, vendor, Money, billing_interval, next_charge_on, plan?, capacity?[]) → Subscription
create_planned_expense(W, Scope, Money, expected_on, category, description)                → FinancialEvent
reserve_budget(W, Scope, Money, purpose, category, period_start, period_end)               → BudgetReservation
allocate_shared_expense(W, subscription_id, allocations: {Scope, method, share_bps}[])     → SubscriptionAllocation[]
propose_ad_budget_change(W, business_id, platform, campaign_id?, current: Money, proposed: Money) → Approval (zawsze; execute w MVP = zapis planu, bez wywołań platform)
request_permission(W, scope, level, resource, limits?, expires_at?)                        → Approval
create_decision(W, Scope, title, rationale, expected_impact?, expected?[], action_ref?, refs?[]) → FinancialEvent(agent_action) (§2.18)
propose_goal(W, Scope, metric, comparator, target_value, currency?, title?, deadline?, agent_id?) → Approval (zawsze 202, action=create_goal; §2.17)

// 0.7.0 (spec §29 What-if, §25 Agent Requests, §28 review)
simulate_scenario(Scope, horizon_days?=7|30|90, changes: Change[1..20]) → Simulation        // READ (finances:observe + forecast:observe); bez zapisu, bez Idempotency-Key
  Change = { kind: "add_expense", amount: Money, on?, category?, label? } | { kind: "add_recurring", amount: Money, billing_interval, interval_count?=1, next_charge_on?, label? }
         | { kind: "cancel_subscription", subscription_id } | { kind: "change_ad_budget", platform, current: Money, proposed: Money /* budżety DZIENNE */, campaign_id?, from? } | { kind: "add_revenue", amount: Money, on?, label? }
  ForecastSnapshot = { closing_cash_minor, min_cash_minor, net_minor, projected_in_minor, projected_out_minor, runway_days|null, free_capital_minor, break_even_on|null, statuses[] }
  Simulation = { scope, currency, horizon_days, from, to, changes[], baseline: ForecastSnapshot, simulated: ForecastSnapshot,
                 delta: { closing_cash_minor, min_cash_minor, net_minor, runway_days|null, free_capital_minor, break_even_on: {from, to} },
                 unmodeled_minor /* część zmiany nieodwzorowana eventami, odjęta analitycznie */, data_class: "estimated", notes: string[] }
request_data(W, kind: "connect_integration"|"provide_data", details: { provider? /* connect */, field? /* provide, np. cash_snapshot */, why, impact_on_coverage? }) → Approval (202, action = kind)
get_decision_review(decision_id)                          → DecisionOutcome (§2.18; własne decyzje)
get_agent_economics(agent_id?, business_id?, from?, to?)  → AgentEconomics (0.7.1, spec §32; agent: tylko własne)

// 1.0.0-rc.2 (Agentowy 66), experimental
get_personal_summary(from?, to?)                          → FinancialSummary dla personal (scope `finances:observe@personal`; brak grantu → scope_denied z what_to_do „poproś o grant @personal")
get_personal_forecast(horizon?: 7|30|90 | {from,to}, include_points?) → Forecast dla personal (jak wyżej)
plan_capacity(needs: {unit, amount, business_id}[1..10], W?) → { per_need: { need, covered_by: {subscription_id, free}|null, shortfall: int, suggestion: "allocate"|"upgrade"|"buy_new", est_cost_minor: int|null, proposed_allocations[] }[], proposals: Approval[] /* tylko z W: Approval allocate_shared_expense, nic nie kupuje */ }
  AgentEconomics = { agent_id, period: {from,to}, currency, cost: { total_minor, runtime_minor /* agent_runtime z ledgera */, tools_minor /* alokacje subskrypcji z SubscriptionAllocation.agent_id */, by_category[] },
                     actions: { total, by_tool: Record<string,int>, denied: int }, approvals: { proposed, approved, rejected, expired, pending },
                     impact: { reviewed_decisions, met, missed, revenue_generated_minor|null, cost_saved_minor|null, data_class },
                     roi: { value: number|null, data_class: "estimated"|"unavailable", note } }

#Transport zdalny MCP (1.0.0-rc.2, Zadanie 81/84)

  • Ten sam serwer operlance działa po stdio (paczka @operlance/mcp, token w env OPERLANCE_TOKEN) i po Streamable HTTP: POST <MCP_PUBLIC_URL>/mcp z nagłówkiem Authorization: Bearer agt_… per żądanie (stateless: brak sesji MCP, sessionIdGenerator wyłączony, odpowiedzi JSON), GET /health{status:"ok", transport:"streamable-http", contract}; żadnych tokenów w env usługi. Staging: usługa mcp (Railway, apps/mcp/Dockerfile.mcp, port 8788) rozmawia z API przez sieć prywatną (OPERLANCE_API_URL=http://api.railway.internal:4000); adres publiczny tymczasowo domena Railway, docelowo mcp.operlance.app (Q20, PRODUCTION §2).
  • Auth i uprawnienia identyczne jak REST (token agenta → Passport; allowed_cidrs widzą IP klienta z X-Forwarded-For); rate limit per token 120/min (na stagingu w pamięci procesu, docelowo rate_limits w DB); brak tokenu/zły token → HTTP 401 z {code:"unauthenticated"}; przekroczenie → 429 + Retry-After; błędy tool jak §11 (isError, what_to_do). CORS wyłączony (klienci MCP to procesy, nie przeglądarki); nagłówki z SECURITY §3; limit body 1 MB.
  • Klienci: claude mcp add --transport http operlance <MCP_PUBLIC_URL>/mcp --header "Authorization: Bearer …"; Claude Desktop/Cursor: { "url": "…/mcp", "headers": { "Authorization": "Bearer …" } } (token wklejany przez Ownera z panelu, nigdy w repo/docs).
Source: KONTRAKT §10 (required scopes) (Polish, verbatim from the contract)

#10. Scopes i poziomy autonomii (spec §22-23)

Scopes (string): finances, subscriptions, ads, forecast, expenses, planned_expenses, budget_reserve, allocations, goals, decisions. Grant = scope × level × resource (all | business:<id> | project:<id> | personal) × limits. Zapis skrótowy: ads:execute@business:<id>.

levelznaczenie
observeodczyt, analiza, raport
recommendobserve + write tworzy Approval (202), nic nie jest wykonywane
executerecommend + write wykonywany od razu w limitach; ponad limit „per action" → Approval; ponad limit dzienny/miesięczny → 403 limit_exceeded
  • Konflikt zasobu między agentami (0.7.1, spec §31): write/propozycja dotycząca tego samego zasobu (kampania, subskrypcja, rezerwacja), dla którego istnieje pending Approval innego agenta → 409 state_conflict z details.conflict {approval_id, agent_id, action} (AuditEntry denied, AuthzDecision.deny.code += state_conflict). Check konfliktu i INSERT Approval wykonywane w jednej transakcji (authorize dostaje tx), żeby dwie równoległe propozycje nie przeszły obie.
  • Limity dzienne/miesięczne liczą także kwoty OCZEKUJĄCYCH Approval agenta (status=pending), ochrona przed podwójnym budżetowaniem (spec §31): usage = Σ wykonane akcje w oknie + Σ pending Approval z amount_minor w oknie; po rejected/expired/cancelled kwota wraca do puli; approved przechodzi z „pending” do „wykonane” bez podwójnego liczenia. limit_checked w AuditEntry rozbija usage na executed_minor i pending_minor. Odmowa limit_exceeded (0.6.1): AuditEntry denied z limit_checked[] (jeden wpis per sprawdzony limit: {limit_key, limit_minor|pct, usage_minor, attempted_minor, currency, window}).
  • Samoobsługowe dla każdego aktywnego agenta (zamierzone, 0.8.1): GET /me, GET /agents/me, GET /agents/:id (własny), GET /agents/:id/passport (własny), POST /permission-requests, POST /data-requests, GET /approvals (własne): nie wymagają grantu, bo agent musi móc poznać własne uprawnienia i poprosić o więcej; zawsze audytowane.
  • Default deny. Brak grantu = 403 scope_denied (z audit denied). personal nigdy nie wynika z all, wymaga jawnego grantu @personal.
  • Grant na business obejmuje jego projekty. Grant wygasły (expires_at) = brak grantu.
  • ads:execute limit max_change_pct_per_day (np. 10 = ±10%).
  • Wymagany scope: czytanie podsumowań finances:observe; create_expenseexpenses; create_recurring_expensesubscriptions+expenses; create_planned_expenseplanned_expenses; reserve_budgetbudget_reserve; allocate_shared_expenseallocations; propose_ad_budget_changeads (≥recommend); create_decisiondecisions; get_decision_outcomes, get_decision_reviewdecisions (observe); simulate_scenariofinances+forecast (observe); request_data→dowolny aktywny agent (jak request_permission); get_goals, get_onboardinggoals (observe; onboarding = własny passport, zawsze dozwolone); propose_goalgoals (≥ recommend, wynik zawsze Approval).