Proposed provisioning and payer contract
AGNT2-532: Free. Billing is not connected yet. No contract, contract budget, card or redirect URI is required to create, activate or provision an organization. Usage does not create debt or future payment obligations. Resource limits, approved policies, operator permissions and explicit platform activation still apply. Future paid usage requires separate opt-in; free-period usage must never be billed retroactively.
Historical design snapshot
This page records the earlier design scope, not current release status. Agentfy now has committed provisioning, scoped authorization and trusted MCP foundations; see the implemented server-chat contract and evidence boundary. Browser/BONSITE protocols and broader billing/lifecycle proposals below retain their own release gates. No live partner deployment is implied.
Atomic provisioning and retries
The {orgId} path parameter must match the organization resolved from the authenticated credential; it never selects or grants access to another organization. See the approved naming convention.
Authenticate the service credential first; derive namespace/environment from it, never from a body organizationId. Validate scopes, subject/team mappings, profile entitlement, limits and billing consent before local allocation. One database transaction commits the external identity/actor (if new), team, owner membership, payer assignment, durable operation and outbox event. An existing actor is reused only within its namespace. All local writes roll back together; remote agent/provider calls happen after commit.
Idempotency receipt uniqueness is (namespace, operationKind, idempotencyKey), independent of rotated credential ID; audit the actual credential on every attempt. Hash a canonical versioned request with normalized defaults; exclude transport headers. Same key/hash returns the same operation, including pending/failed state. Same key/different hash is 409 idempotency_conflict. Recommended receipt retention is 30 days, but permanent external-ID mappings and original bootstrap fingerprint prevent duplicates after expiry. If a response is lost, a retry or operation lookup retrieves committed state.
Concurrent keys claiming the same external team serialize on its unique namespace/team ID. Same bootstrap fingerprint returns the original operation; different creator/profile/payer bootstrap facts produce 409 external_id_conflict while the original reservation is live. Explicit retirement permits a new agent/profile lifecycle while retaining the team, subject and payer (see below). Repeated bootstrap never re-adds removed membership, changes payer or resets a suspended/deleted team. Authorized changes use dedicated mutation endpoints with expected versions. Missing or foreign resource references return nondisclosing 404; do not leak a linked direct account through conflict details.
Outbox workers use operation + step idempotency keys, claim leases and retry safely after crashes. Agent allocation has stable team/external-agent uniqueness. Persist pending, ready, failed or retired, step, safe reason and retryability; ready requires actual agent setup completion. A failed setup must not leave paid automation running: paid dispatch is gated on readiness, valid payer and budget. Provider setup uses its own idempotency reference and reconciliation, never a distributed database transaction or a second charge on retry.
Concrete proposed requests
POST /v1/orgs/{orgId}/provisionings
Authorization: Bearer <provisioning-key>
Idempotency-Key: workspace-77-v1
Content-Type: application/json
{
"subject": { "externalUserId": "opaque-person-42" },
"team": { "externalId": "workspace-77", "name": "Workspace 77" },
"agent": {"externalId": "assistant", "profile": {"name": "Store assistant", "instructions": "Help the authorized user with the store.", "skill": {"name": "store-help", "description": "Store assistance", "body": "Use the attached MCP for authorized store requests."}}},
"payer": { "mode": "organization" }
}{
"success": true,
"data": {
"operationId": "operation-7",
"userId": "user-partner-42",
"teamId": "team-77",
"status": "pending",
"billingStatus": "active",
"version": 1
}
}The response contains only the partner actor ID, never a linked direct ID/email or payment reference. First acceptance is 202; safe retries return the same operation (202 while pending; 200 when terminal). GET /v1/orgs/{orgId}/operations/operation-7 returns its current status and stable IDs; failed operations expose safe reason, retryable and a correlation ID, not secrets. Retryable failures resume the same operation; nonretryable conflicts require an explicit correction workflow.
For team-paid creation replace payer with {"mode":"team","consentId":"consent-9"} or {"mode":"team"}. A consent is single-use, bound to namespace, external team ID, proposed billing terms and independently authenticated direct payer; commit consumes it atomically. Without consent, return billingStatus: "pending_acceptance", record the creator as intended responsible actor, preserve free entitlements and prohibit paid work. Do not represent that intent as an accepted personal mandate. The effective team assignment remains team-paid/pending; consent later activates its backing BillingAccount without creating another team or retroactive debt.
POST /v1/orgs/{orgId}/browser-authorizations
Authorization: Bearer <separate-identity-issuer-key>
Content-Type: application/json
{
"transactionId": "browser-transaction-8",
"externalUserId": "opaque-person-42",
"teamId": "team-77",
"nonce": "transaction-bound-nonce",
"purpose": "login"
}The server additionally validates the backchannel proof and browser transaction described in the browser flow. Success returns a single-use callback code and the already registered redirect target, never a user access token. BONSITE may not provide an arbitrary redirect or use this operation to create a direct/global session. Code redemption is Agentfy-browser-bound, not a general partner token endpoint.
| Proposed endpoint | Required authority and checks |
|---|---|
POST /v1/orgs/{orgId}/provisionings | provision:create; own namespace, allowed profile, free organization coverage within approved resource policy; no browser scope |
GET /v1/orgs/{orgId}/operations/:id | provision:read; own namespace, sanitized response |
POST /v1/orgs/{orgId}/operations/:id/retry | provision:create; retryable step, same operation, current status/version |
POST /v1/orgs/{orgId}/browser-authorizations | Separate identity:authorize; active bound browser transaction, subject and membership |
POST /v1/orgs/{orgId}/subjects/:id/suspend | identity:manage; own subject; revoke contexts/agents as required |
POST /v1/teams/:id/invitations | Explicit owner/admin membership, target/role policy, expected team version |
POST /invitations/:id/accept | Fresh proof of exact intended actor, one-use acceptance, live grantor authority |
POST /account-links / DELETE /account-links/:id | Independent direct authentication; create also needs fresh partner proof, explicit confirmation and link lock |
POST /contexts | Direct proof + one active link + current policy/membership; never partner-to-direct elevation |
POST /v1/teams/:id/owner-transfers | Active owner, fresh proof, explicit recipient and resulting role |
POST /owner-transfers/:id/accept | Intended active member, fresh proof, versioned team transaction |
POST /v1/teams/:id/payer-changes | Owner or delegated org billing operator requests; request is not consent |
POST /payer-changes/:id/accept | New direct payer independently signs in, or org billing authority accepts for its organization; provider readiness checked |
PATCH /billing-accounts/:id/contact | Account billing authority; contact proof as appropriate; does not transfer responsibility |
Every mutation has a CSRF-safe browser path or scoped server path, idempotency and expected-version checks where it changes existing state. Unknown scopes are denied; provisioning keys cannot call account-link, credential, personal billing or platform-admin routes. Browser credentials never receive partner server secrets.
Paying is separate from owning
Billing → Who pays shows organization, current responsible party, contact, payment readiness, effective dates, pending proposal, outstanding historical debt and covered/free status. Change billing contact changes where notices go; it does not assign liability or a payment method. Transfer payment responsibility names the proposed payer and cutoff, then explains “Previous invoices remain with the previous payer.”
The new payer uses Review and accept, signs in independently, sees the team/organization, currency, plan/usage terms, budget and effective date, and authorizes a valid provider mandate. A payer need not join the team; a minimal billing invitation exposes financial terms, not customer content. The previous payer retains invoice-only access after losing team membership, with no restoration of content access. Default invitation expiry is 7 days. Decline/expiry/cancel leaves the old assignment effective. Accepting does not grant team or org roles. A partner key cannot accept for someone's direct account, even if that person owns the team.
Default switch time is next billing period boundary. Acceptance creates a scheduled assignment only after provider setup succeeds and terms remain current; at activation revalidate both consent and mandate. Lock the team billing timeline and compare its version; one pending change at a time, replacement explicitly cancels the old proposal. A concurrent or stale acceptance receives 409 and a fresh review screen. There must never be a gap or overlap in effective assignments: close the old [from, cutoff) and open the new [cutoff, to) in one transaction. If activation fails, retain the previous assignment only while its mandate/agreement remains valid; otherwise set nonchargeable coverage status and refuse paid work. Never silently extend a terminated payer's liability.
Future paid model only, not current free onboarding: Covered by organization requires both authorized team/org policy and organization financial authority with an active agreement and budget. BONSITE's preauthorized provisioning agreement supplies default org consent within its limits. An Agentfy platform administrator can authorize internal sponsorship; record a sponsorship reason and cost center. Sponsorship tracks cost, not revenue or collected cash, and generates no invoice to Agentfy itself. Removing coverage requires a replacement payer's consent or stopping paid work; it cannot silently charge the owner.
Organization invoices group only that organization's externally payable assignments, with team/agent breakdowns. Team-paid teams have separate bills to their responsible accounts; they are excluded from the organization's invoice. USD 30 + 70 yields an illustrative USD 100 organization statement only when both teams were organization-paid for that interval. Mixed example: organization-paid A30 + B70 and team-paid C20 gives BONSITE invoice100 and C invoice20; an organization usage report may show120, but that is not BONSITE debt. Team-paid invoices remain per team even when one direct user pays several teams. Grouping/dedup keys include billing mode and teamId for team mode, alongside payer/currency/period/contract. Internal sponsorship adds no revenue. Never copy a payment method or transfer a provider subscription to another legal payer. Create a new authorized mandate/subscription and coordinate scheduled cancellation of the old one; lack of provider support blocks that paid transition.
Usage, cutoffs and budgets
Each charge freezes team, organization, payer-assignment ID, price version, currency and occurrence time. Attribute late events by trusted server occurredAt and historical assignment, not arrival time or today's payer. At a cutoff, split metered intervals or settle bounded reservations on each side; work cannot silently cross into a new payer's interval on the old authorization. Provider billing periods and assignment cutoffs must align before activation. Unpaid debt, refunds and historic invoices stay attributed to the original payer; corrections use adjustment entries, never historical rewrites.
Deduplicate usage by stable event ID and provider callbacks by provider/event ID; validate signatures, persist intake before acknowledging, process transitions monotonically and reconcile out-of-order events against provider state. Retry callbacks must not duplicate invoices or money. Keep integer pico-USD through pricing/aggregation, convert/round once according to currency minor units at invoice boundaries, retain the rounding adjustment and prohibit unapproved currency mixing. Do not infer collected revenue from a usage total or sponsorship entry.
Paid dispatch reserves against an atomic hard budget before work starts; reserve conservatively for bounded work and reconcile actual cost afterward. The payer budget uses billable sell-price amounts in the billing-account currency: available budget equals limit minus settled charges minus open reservations. Vendor costUsdPico safety limits are separate internal controls, not the customer’s spending amount. Concurrent requests cannot both spend the last unit. Split/check long-running work at bounded steps; release unused reservation after definitive completion or safe reconciliation, not merely a timeout while a provider may still charge. If payer, mandate, coverage or budget is invalid, refuse new paid work with Payment setup required / Budget reached; existing free plans remain available within their real entitlement. All of this requires future implementation and provider validation.
Describe the agent inline
The platform approves the organization's policy once. Send agent.profile in each provisioning request: {name, instructions, skill: {name, description, body}, modelId?, mcp?: {url, requiredTools, trustedContext?}, worker?: {tools, egress?}}. Content changes within policy need no platform approval. Omit mcp for a conversation-only agent.
requiredTools is the minimum that must be present, not an allowlist: every discovered tool is available, subject to the usual caller permissions and tool-name collision rules. Every successful PUT …/profile refreshes an unchanged MCP connection after the profile is saved; connection changes still use the setup worker. Changing only requiredTools also refreshes without suspending a working agent.
For all attached MCP servers (including manually connected servers), the first eligible turn after MCP_TOOLS_REFRESH_SECONDS (default 300 seconds) starts a background refresh. That turn keeps its saved tool list; new tools become available on the next turn after refresh completes. Idle agents do not poll. A failed refresh is retried after the same interval; an explicit profile save or Refresh bypasses the age check. Discovery uses a 5-second timeout per HTTP exchange in the background, so it adds no network wait to the triggering turn.
Refresh rejects metadata containing the supplied credentials or a list missing any required tool. The last successful list and discovery timestamp remain intact; lastError is shown in the agent's MCP settings and cleared after a successful refresh. This client does not keep a connection open between requests, so notifications/tools/list_changed is not subscribed to; profile saves and age-based refresh provide updates.
The model must belong to allowedModels; the MCP host must pass the public-address floor and, when mcpHostsRestricted is enabled, fit mcpHosts; trusted context requires trustedContextAllowed; worker tools and egress must fit workerTools and workerEgress. The deployment egress ceiling still applies. Name ≤120, instructions ≤16,000, skill name ≤64, description ≤1,024, body ≤32,000 characters; required MCP tools 1–64, worker tools 1–16, egress entries at most 64.
Admission violations return HTTP 422 with code: policy_violation and message: policy_violation: <field> before allocation. Fields include policy, modelId, mcpHosts, mcp.url, trustedContextAllowed, workerTools, workerEgress. Correct the profile or obtain an approved policy change and resubmit. Model catalogue/plan entitlement is still checked during setup.
The same inline content is accepted by the additional-agent endpoint. Omit team and payer there. Without MCP, no configuration call or discovery is needed. With MCP, configure the credential below and wait for authenticated discovery.
The accepted content is frozen in the operation snapshot and request fingerprint. Replays keep that snapshot after policy changes. A changed request under the same key returns IDEMPOTENCY_CONFLICT; changed intent for a live external reservation returns EXTERNAL_ID_CONFLICT. Content changes to a live agent belong to the separate in-place update workflow, not retirement. Retirement is for intentional removal/replacement.
agent.profile is required. Omit modelId to use the first model in the approved policy. Removed templateId and templateVersion fields return 422, as do other unknown request fields. There is no saved content catalogue or default selection.
In the Policy tab, All tools stores workerTools: ["*"]: every current and future runtime worker tool is allowed by the policy. The per-tool checklist is disabled while checked. An agent still requests its own explicit tools; network destinations and the deployment ceiling remain separate limits.
Admin → Organizations → Provisioned agents shows each operation and all accepted profile revisions, with timestamps and submitting key IDs. PUT /v1/orgs/{orgId}/operations/{operationId}/profile accepts { expectedVersion, profile } using a provisioning key. It replaces the complete profile of a ready operation under the current policy, retaining agent identity, conversations and original POST fingerprints. Stale versions return 409 OPERATION_VERSION_CONFLICT. Connection changes become pending for revalidation; content-only changes stay ready. See onboarding step 8.
Retire a provisioned agent
DELETE /v1/orgs/{orgId}/teams/{teamId}/agents/{externalAgentId} requires the organization's provisioning credential and provision:create. It returns 204, including repeat calls on the retired reservation. Unknown/foreign team or external ID returns the same opaque provisioning 404. Credentials without provision:create cannot use this endpoint; organization status, environment and credential checks are unchanged.
Retirement archives rather than deletes the Agent, disables its heartbeat, revokes its secrets and provisioning MCP attachment, closes partner conversations and stops runtime sessions. GET of the old operation returns retired with retryable: false. Audit retains credential, organization, team, agent and operation. If runtime cleanup fails, DELETE returns 503 and can be repeated to resume; the reservation is not released until cleanup succeeds.
A new provisioning request/key for the same external agent ID allocates a new operation and agent using the submitted policy-compliant profile. Only live reservations participate in fingerprint replay; old request-key receipts remain bound to their original operations. Tombstoned history is immutable and retained, with a partial unique index allowing one live reservation per external agent ID. Old conversations cannot be used with the replacement. Once replaced, DELETE of the same path targets the replacement, not its predecessor.
Step 1 — approve the organization policy
In the app, open Organizations → your organization → Policy. An owner or admin lists the allowed models, MCP hosts, trusted context, worker tools, worker destinations and maximum teams, then saves and submits the draft. A platform administrator opens the policy queue under Organizations in the admin panel, reviews the diff and approves or rejects with a reason. Only an approved policy permits provisioning; a pending replacement leaves the previous version effective.
Provision using an inline profile. A failed operation with policy_violation: <field> names the permission that needs repair. Widen and approve the policy, then retry the operation using its current version. Worker destinations must also fit inside RUNTIME_WORKER_EGRESS_ALLOWLIST; public-address checks remain in force.
MCP domain restriction is optional: new policies in the editor send mcpHostsRestricted: false, so a public MCP URL in the agent profile needs no organization host entry. Enable Allow only specified MCP domains in advanced policy settings to enforce mcpHosts; an enabled empty list blocks every MCP connection. Existing policies and legacy API requests omitting the flag stay restricted, including empty lists. Changing the mode takes effect only after submission/approval; all other approval gates, URL validation, private-address protection and platform/worker network limits remain in force.