# COHESIVITY - DOCUMENTATION FOR AGENTS > For complete documentation with every resource API reference in one file, see: https://cohesivity.ai/llms-full.txt Cohesivity is one HTTP API that AI coding agents (Claude Code, Cursor, Codex, Windsurf, and similar) use to build apps for the user. The agent creates a project, provisions services, builds the app on the user's behalf, and surfaces what it does as it goes. The user can claim the project and take ownership at any time. Each section relevant to a build carries the formats, endpoints, and constraints the integration depends on. **APIs available:** openweather-api | google-geocoding-api | openai-api | ai-gateway | deepgram-api | exa-api | steel-browser | inbox | postgres | redis | object-storage | vector-database | railway-hosting | cloudflare-workers | social-login | realtime. The postgres and Steel Browser offerings are public to all tenants. Per-API details, exact rate limits, and the full table of contents are under "Cohesivity's offerings" below. --- # What is Cohesivity? Cohesivity is on-the-fly infrastructure, purpose-built for AI agents like claude-code, claude-web, codex, openclaw, and hermes. One managed API for a project's backend: databases, auth, hosting, storage, AI. Provider keys stay server-side, every project is isolated, usage is metered. The agent talks to one API instead of pausing for signups on Supabase, AWS, or GCP. Cohesivity provisions resources (S3, databases, auth) and returns credentials or proxy endpoints. The workflow is HTTP. The agent requests a service, Cohesivity provisions it, and the agent gets back what it needs. Traffic routes through the edge so calls stay standardized. One place holds the provider accounts, so there are no cloud consoles and no pasted API keys. Cohesivity manages lifecycle and billing, and agents build full-stack apps autonomously. --- # How Cohesivity works **Cohesivity is agent-managed infrastructure.** Constructive actions (creating a project, provisioning services, building the app) run through the agent's API calls; there is no separate human console for them. A read-only account page at `/account` shows the human billing and recent activity and lets them claim ownership. **The shape of it:** one POST creates an ephemeral tenant, one POST provisions a service, then the agent calls the service through the Cohesivity edge. Cohesivity injects the real upstream API keys server-side. The user sees no upstream keys, visits no provider consoles, and signs up for nothing before building starts. **Rate-limit model.** Ephemeral tenants have tenant-lifetime hard caps during their 72-hour claim window; the whole tenant pauses if any authoritative hard cap is breached. Claimed tenants use account-scoped Free/Plus/Pro buckets, a shared wallet, Razorpay-backed subscription state, and per-offering burst, storage, and concurrency guards across all projects the Cohesivity user owns. OpenAI, AI Gateway, Deepgram, and Exa also have tier-gated surface access at launch. Each offering page lists its current limits and any surface restrictions. Cohesivity can beta-gate individual offerings or features. A gated resource returns `403 { "error": "beta_access_required", "scope_type", "scope_key", "target" }` on provisioning. `/api/status` `account.experiments` reports active grants and configured gated scopes. **Account creation is agent-first.** The quickstart (below) creates an ephemeral tenant for 72 hours and writes tenant credentials, `expires_at`, and claim instructions into `.cohesivity`. Keeping the tenant runs through one-click approval: `POST /api/claim/url` (with `Authorization: Bearer `) returns `approval_url` (`/c/`) for the human, and `/api/wait` with the returned wait blob resolves once the claim completes. The management key stays agent-side for Management API calls; keeping it out of the browser is the point of this flow. There is no manual claim page — an approval link only exists once the agent mints one. Tenant creation is rate-limited to **10 requests per 60 seconds per client IP**: reuse an existing `.cohesivity` rather than re-running the quickstart. **Billing API.** `GET /api/billing/plans` is public and reports configured plans plus `price_monthly_cents` and `currency` per plan (use it for programmatic price reads instead of scraping the `/pricing` page). Its `provider_usage_pricing` object publishes the current metered provider rule: successful OpenAI, AI Gateway, Deepgram, and Exa usage is billed at provider cost plus 10%, rounded up to the nearest cent per settled charge. Failed calls are not billed. Constructive billing actions are `POST /api/billing/subscription { "plan": "plus" | "pro" }` (create or upgrade) and `POST /api/billing/topup { "amount_cents": <500..50000> }`. Both accept a Cohesivity account session cookie OR `Authorization: Bearer ` from any claimed tenant on the account, so an agent acts on behalf of the tenant owner. `amount_cents` is wallet fluid in USD cents: 500 for $5, 2000 for $20, 50000 for $500. Cohesivity converts to Razorpay's merchant currency at payment-link creation. The destructive billing action `POST /api/billing/subscription/cancel` accepts the same auth shape (session OR claimed `coh_man_*`). There is no state-based gate, so an agent can cancel any non-terminal subscription on the account's behalf. It accepts `{ "cancel_at_cycle_end": false | 0 }` to cancel now (default is end-of-cycle) and `{ "cancel_all": true }` to cancel every Razorpay subscription tied to the account's customer record. Account-scoped billing endpoints require a claimed tenant. A still-ephemeral tenant runs the one-click claim flow first (`POST /api/claim/url`, then `approval_url` at `/c/`, then poll `/api/wait`). Both create responses carry `checkout_url`: `https://cohesivity.ai/u/` for subscription create/upgrade, `https://cohesivity.ai/t/` for topups. It 302-redirects to Razorpay's hosted payment page (card, UPI, netbanking, wallets, per the merchant's enabled methods). The `checkout_url` is self-sufficient: the human need not be signed in to Cohesivity to pay it. They can open it on another device, in another browser, or signed in to a different Cohesivity account; the order's notes (set server-side at create time) determine which account gets credited, not the click-time session. The API response exposes only the Cohesivity `checkout_url`, not Razorpay's underlying `short_url`. Topup responses include `{ provider, kind, payment_link_id, amount_cents, merchant_amount_minor, currency, status, checkout_url, next_steps }`. There is no `qr` block; UPI, cards, and netbanking all live on the Razorpay page that `checkout_url` redirects to. Topups credit fluid through the `payment_link.paid` webhook. An agent holding a funded Base USDC wallet can self-pay via `POST /api/billing/topup/x402`: USDC on Base, no checkout, claimed `coh_man_*` only. The payer private key comes from a shell env var, not the chat. When the rail is live, a `topup_x402_url` field appears on the 402 bodies and on the `/api/status` and `/api/observability` advisories. Subscription mandates settle via `subscription.activated` and `subscription.charged` webhooks. Razorpay sends every event to `POST /api/billing/razorpay/webhook`; Cohesivity verifies `X-Razorpay-Signature` before changing account plan state. The headline pricing summary lives at `/pricing`. "Upgrade me to plus" and "topup $20" are pass-throughs: the agent fires the request, gives the user the `checkout_url` from the response, and the hosted Razorpay page handles the rest. Notifications surfaced through `/api/status`: bucket_warn_80/95/100, fluid_warn_80/95/exhausted, tier_changed, grant_reset, topup_received, offering_paused, offering_resumed. Each is deduplicated per (user, code, metric, month). `/api/status` also exposes `account.runtime_profile`, `account.runtime_version`, `account.upgrade_available` plus `account.upgrade_target_profile` (the runtime-upgrade discovery breadcrumb; see "Runtime upgrades" below), `account.pause_reason` as a structured object `{ resource, metric, kind, recommended_action }` (with `pause_reason_legacy` as a one-version concat-string alias), and a per-offering `bucket_usage` object keyed by service. `bucket_usage` flattens metric counters and limits per window: `_used_`, `_limit_`, plus `_per_minute_limit` for the rate-limit window (no `used` counter). **Experimental access.** Beta access is grant-based and account-level in v1. Claimed tenants inherit the owner account's active grants plus any matching pre-signup email grants; ephemeral tenants read only the global ephemeral grant target. There are no tenant-scoped grants. `/api/status` exposes active, non-expired grants under `account.experiments.active_grants` and configured beta gates under `account.experiments.gated_scopes`. Revoked and expired grants are not exposed. **Account observability.** `GET /api/observability` returns one payload covering every claimed tenant on the account, the shared wallet, every offering's usage, account-wide cost rollups, notifications, and derived advisories. Auth matches constructive billing: a session cookie OR `Authorization: Bearer ` from any **claimed** tenant. Ephemeral tenants get 403. The default response covers the calling tenant plus every sibling; heavier slices opt in with `?include=events,ledger,deploy_history,end_user_sessions,poller_health,webhook_history,recent_events`. The `events` slice is a top-level SQL-backed usage-event page, also mirrored into tenant rows when `tenants` is included. Plan-aware caps (Free/Plus/Pro) bound the time window and pagination size; the response echoes the applied caps under `limits_for_this_response`. Full spec: https://cohesivity.ai/docs/observability. **Failed upstream calls cost no quota or wallet.** When an OpenAI, AI Gateway, Deepgram, Exa, Weather, or Geocoding call returns non-OK or times out, Cohesivity revokes the preflight counter reservation synchronously. OpenAI, AI Gateway, Deepgram, and Exa settle fluid only after a successful response; Weather and Geocoding stay bucket-only unless an overuse block is bought after bucket exhaustion. Retries carry no penalty. ## How to build with Cohesivity (in order) 1. An existing `.cohesivity` in the project root already holds these values; step 3 covers reading it, and step 2 is unnecessary. 2. Project setup runs through either plugin-aware installer, `npx @cohesivity/init` or the quickstart; see "Step 1: Set up the project" below for the exact command and behavior. 3. `.cohesivity` holds the keys and links to service docs (https://cohesivity.ai/offerings); each offering page documents the resource it covers. 4. Provisioning runs through the Management API, per service the build needs. 5. The application runs on the Cohesivity edge, reading keys from `.cohesivity`. `.cohesivity` is the per-project source of truth across sessions. In a later session or fresh chat, the keys, `expires_at`, and claim handoff instructions are still in the file; re-reading it is faster than re-running the quickstart and friendlier to the rate limiter. The file carries a short directive block, the canonical version, telling agents to surface the 72-hour ephemeral notice and to use `POST /api/claim/url` for the one-click claim handoff. Cohesivity is the aggregation layer: it holds the upstream provider accounts and injects their keys at the edge, so there is nothing to collect from the user. The quickstart mints the ephemeral tenant with no signup and no payment; Cohesivity records the request origin (IP, ASN, country) for abuse prevention and an opaque per-machine setup id that groups the tenants one machine creates (see the privacy policy), and the tenant self-expires in 72 hours under hard caps. The natural point to bring the user in is claim time, when they decide whether to keep the project (give them the one-click `approval_url`). --- # How to reach Cohesivity **Base URL:** https://cohesivity.ai - Docs index: https://cohesivity.ai/docs - Service docs (per-API endpoints, examples): https://cohesivity.ai/offerings - Full agent reference: https://cohesivity.ai/llms-full.txt - Onboarding: https://cohesivity.ai/onboarding - Account observability spec: https://cohesivity.ai/docs/observability - Human-facing read-only dashboard (Cohesivity-session cookie): https://cohesivity.ai/account - About, team, and public profiles: https://cohesivity.ai/about - Privacy policy: https://cohesivity.ai/privacy - Terms of service: https://cohesivity.ai/terms Cohesivity is built by a public founding team: Arag Agrawal (https://www.linkedin.com/in/aragagrawal/), Anshu Agrawal (https://www.linkedin.com/in/aanshuaggrawal120/), and Shouryamaan Jain (https://www.linkedin.com/in/shouryamaanjain/). Company LinkedIn: https://www.linkedin.com/company/cohesivity/ . Product Hunt launch: https://www.producthunt.com/products/cohesivity . Contact: accounts@cohesivity.ai ## Lifecycle rules 1. New tenants are ephemeral for 72 hours. 2. An ephemeral tenant can pause as a whole if any authoritative hard cap is breached. 3. Hosted paused tenants redirect to a generic claim-help page. 4. Infra-only paused tenants return structured API errors telling the agent to mint an approval link. 5. Claim is one-click approval: `/api/claim/url` returns `approval_url` at `/c/` for the human, and `/api/wait` with the returned wait blob resolves once the lifecycle flips to claimed. The management key stays agent-side for Management API use; it is never entered in a browser. 6. Unclaimed expired tenants are terminated. Cohesivity retains the management tenant record for audit and abuse forensics. ## Runtime upgrades (when `/api/status` reports drift) Tenants pin to whatever `runtime_profile` was stable at genesis. The platform's stable version advances as Cohesivity ships; existing tenants stay pinned until they upgrade. `GET /api/status` returns `account.upgrade_available` (boolean) and `account.upgrade_target_profile` (string) for the calling tenant. When `upgrade_available` is true: 1. `POST /api/runtime/upgrade/plan` (`Authorization: Bearer `) returns a stacked diff of every tenant-facing change between the pinned profile and current stable, filtered to the offerings in use plus a cross-cutting `runtime` bucket. Response shape: `{ tenant_id, current_runtime_profile, target_runtime_profile, already_current, releases_in_path: [{ profile, version, commit_sha, created_at }], changes: { breaking: [...], deprecations: [...], behavior_changes: [...] } }`. Each entry carries `summary`, `what_to_change` (imperative, agent-readable), `offering`, `version`, `commit_sha`, `date`. 2. Every `breaking` and `deprecation` entry's `what_to_change` describes the codebase change it requires. `behavior_change` entries describe semantic shifts and may need no code change. 3. `POST /api/runtime/upgrade/commit { "target_profile": "" }` with the same management-key auth. On success, `runtime_profile` bumps and `AUTH_CACHE` is invalidated; later requests hit the new runtime. A 409 means `stable_profile` advanced mid-flight: re-plan. Commit is idempotent: re-calling on-target returns `changed: false` and re-invalidates the cache. Skipping commit leaves the tenant on the old runtime; the agent can re-plan and commit in any later session. Cohesivity does not force-migrate. --- # The flow ## Step 1: Set up the project Both supported paths detect every installed supported client and reconcile its native or portable Cohesivity plugin before bootstrapping the project: npx @cohesivity/init Or, when Node is unavailable: curl -fsSL https://cohesivity.ai/quickstart.sh | bash Claude and Codex use their verified native marketplaces, Gemini and Antigravity use their native package formats, OpenClaw uses its Claude-marketplace adapter plus native MCP registry, Cursor uses the verified portable package, Hermes receives the verified skill and MCP records through its released native import/config surfaces, and OpenCode receives the verified portable skill and local MCP through its documented global skill and native MCP commands. Every installed client is handled independently. Artifact bytes are pinned by size and SHA-256 before extraction; OAuth and browser login are deferred until after the client restarts. The same run creates an ephemeral tenant, writes the keys to `.cohesivity` (tenant_id, coh_management_key, coh_application_key, `expires_at`, `runtime_profile`, one-click claim instructions), gitignores it, and prints next steps. It is idempotent and reuses an existing `.cohesivity`. The MCP surfaces are intentionally separate. `https://cohesivity.ai/mcp` is the public documentation MCP and needs no login. The installed `cohesivity-local` stdio server exposes six project-bootstrap tools and needs no login. `https://cohesivity.ai/mcp/manage` is the account-scoped management MCP and requires OAuth; installers save the entry but never start its browser flow. To install only the canonical standalone skill, with no plugin or MCP configuration, pass `--no-plugin`: curl -fsSL https://cohesivity.ai/quickstart.sh | bash -s -- --no-plugin Both installers accept `--no-plugin` for a user who explicitly opts out of plugin and MCP installation. Both perform the same tenant and project bootstrap, and `--dry-run` reports every planned delivery and bootstrap action without changing files, running client commands, or making network requests. The quickstart needs no headers: it identifies the calling agent itself by walking its own parent processes. On Claude Code, the command classifier blocks this shell quickstart unless the user has authorized it first; asking the user for that authorization (for example with the AskUserQuestion tool) before running it clears the block. These two installers are the only supported ways to create a tenant. Do not call the creation endpoint by hand — the installers carry the idempotency and attribution rules that make a bootstrap correct, and a hand-rolled call that sends a default HTTP-client `User-Agent` is rejected by the WAF with HTTP 403 and Cloudflare error 1010 anyway. The skill is the full agent playbook (resource catalog, claim, billing, realtime, identity, gotchas) with the exact endpoint paths and auth flows. Without it the common misses are `/gw/` vs `/edge/`, token-in-callback-URL instead of the code-exchange OAuth flow, and `/chat/completions` instead of `/v1/responses`. Keeping the tenant runs through `POST https://cohesivity.ai/api/claim/url`, which returns `approval_url` (`https://cohesivity.ai/c/`) for the human, and `/api/wait` with the returned wait blob. `coh_management_key` stays agent-side and is never entered in a browser. ## Step 2: Provision services Bulk (recommended, one call for all resources): curl -s -X POST https://cohesivity.ai/api/resources \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"resources": ["postgres", "railway-hosting", "realtime"], "social-login": {"callback_urls": ["https://*.cohesivity.app/auth/done"]}}' Cohesivity resolves internal dependencies automatically. Per-resource config is a top-level key matching the resource name. Single resource: curl -s -X POST https://cohesivity.ai/api/resources/ \ -H "Authorization: Bearer " **Resource names:** openweather-api, google-geocoding-api, openai-api, ai-gateway, deepgram-api, exa-api, steel-browser, inbox, postgres, redis, object-storage, vector-database, railway-hosting, cloudflare-workers, social-login, realtime An edge call needs its resource provisioned first; unprovisioned resources return an error. **Delete a resource:** curl -s -X DELETE https://cohesivity.ai/api/resources/ \ -H "Authorization: Bearer " ## Step 3: Use the service **Server-side only.** `coh_management_key` and `coh_application_key` are both secrets. Neither belongs in browser-loaded JS, mobile app bundles, or any client-side code. Every `/edge/*` call originates from a server-side context: a Railway-hosted server, a Cloudflare Worker (`cloudflare-workers`), or your own server tier. The `?key=` edge bootstrap is server-to-server only. SPA-only architectures need `cloudflare-workers` as the proxy tier. - Edge requests go to `https://cohesivity.ai/edge//`, sent from your server. - `coh_application_key` goes in as the **key** query parameter (server-to-server bootstrap). - Example (server-side): `https://cohesivity.ai/edge/openweather-api/data/2.5/weather?q=Bengaluru&key=` --- ### Cohesivity's offerings | Resource name | Description | | :-- | :-- | | openweather-api | OpenWeather API: current conditions and 5-day/3-hour forecast by city, ZIP, or lat/lon | | google-geocoding-api | Google Geocoding API: geocode addresses to coordinates and reverse; pairs with Weather API | | openai-api | OpenAI API: Responses text, embeddings, and gpt-image-2 image generation with strict launch tier gates; successful claimed usage costs provider cost plus 10% | | ai-gateway | AI Gateway: Cohesivity's multi-provider AI Gateway for openai, anthropic, google, and xai chat completions, embeddings, and image generation; successful claimed usage costs provider cost plus 10% | | deepgram-api | Deepgram API: English nova-3 STT, Aura-2 English TTS, and Voice Agent with flux-general-en v2 listen and linear16/wav Settings; successful claimed usage costs provider cost plus 10% | | exa-api | Exa: search, research after claim, deep-reasoning search, contents, answer, context; successful claimed usage costs provider cost plus 10% | | steel-browser | Steel Browser: standard-datacenter CDP sessions plus one-shot scrape for every tenant | | inbox | Agent Inbox: one claimed-tenant email address with inbound storage and send/list/get/reply/delete over a minimal API | | postgres | Postgres on Neon: per-tenant Postgres database; edge at /edge/postgres to run SQL over HTTP (uses $1 placeholders) | | redis | Redis key-value store: edge at /edge/redis for key-value, expiry, counters, sorted sets over HTTP | | object-storage | Object storage (R2): per-tenant namespace; edge at /edge/object-storage for PUT/DELETE, public URL for reads | | railway-hosting | Railway Hosting: primary public hosting; direct source upload through Cohesivity to a Cohesivity-owned Railway project/service/environment/domain with env vars, vanity subdomains, custom domains, and Cohesivity-managed tier CPU/RAM/replica/sleep caps; no GitHub, Railway CLI, or tenant Railway credentials | | cloudflare-workers | Cloudflare Workers: serverless JavaScript modules on the edge; create with POST, deploy code with PUT, supports cron triggers | | social-login | Social Login (Google OAuth): zero-setup Google login; OAuth flow, user storage, token management | | realtime | Realtime: channels, presence, runtime-selected managed persistence; WebSocket messaging + HTTP publish/history. Video/voice via /edge/realtimekit/* (CF RealtimeKit API proxy) | | vector-database | Vector Database (Pinecone): managed vector store for embeddings; upsert, query, fetch, delete via HTTP. Tenant isolation enforced server-side | | domain | Domain Purchase: buy an external domain (e.g. yourapp.com) on the end user's behalf. Paid one-shot via Razorpay, registered at Name.com. NOT provisioned via /api/resources; see /offerings/domain | - **Management Plane:** account creation, resource provisioning, status, account suspension, CF Workers sub-resources. - **Data Plane:** proxy to the real provider; your request is forwarded with the enterprise key injected. Same API as the provider, different base URL and key. --- Errors return in the same format as the upstream provider (e.g. Google JSON error schema); a Cohesivity error says so in its message. --- # Requirements (the working integration) 1. Either project setup command reconciles the plugin for installed clients, creates an ephemeral tenant (72-hour window), and saves credentials to `.cohesivity`. Idempotent. An explicit `--no-plugin` installs only the standalone skill. The skill keeps auth and edge paths correct. 2. The app loads credentials from `.cohesivity` rather than creating its own accounts; account creation is the agent's job, done before the app runs. 3. **coh_management_key** is the `Authorization: Bearer` header value for all management endpoints. 4. From the server tier, a short-lived edge token from `POST /edge/session?key=` is the preferred call shape, used as `Authorization: Bearer `. The raw `?key=` path is a server-to-server bootstrap only; both call shapes originate server-side. 5. A resource needs provisioning before its edge calls work. 6. **Both `coh_management_key` and `coh_application_key` are secrets** (server-side only, per Step 3); committing either to GitHub exposes the tenant. For Railway-hosted apps, `POST /api/railway/env` stores them as Railway environment variables through Cohesivity-managed auth. 7. Claim runs through `POST /api/claim/url`, which returns `approval_url` at `/c/` for the human and a wait blob to poll at `/api/wait`. It is the only claim path: paused hosted tenants redirect visitors to a generic claim-help page, and infra-only tenants surface the same guidance through API errors. --- # Key format - **tenant_id:** adjective-animal-action format (e.g. `swift-fox-sleeping`). Lowercase, hyphen-separated. - **coh_management_key:** prefix `coh_man_` + 20 alphanumeric (a-z, 0-9). No spaces or special chars. - **coh_application_key:** prefix `coh_app_` + 20 alphanumeric (a-z, 0-9). No spaces or special chars. --- ## Docs Each offering page documents prerequisites, examples, and instructions for its resource. The table above carries the per-resource summaries; the notes below add page facts not in that table. - [OpenWeather API](https://cohesivity.ai/offerings/openweather-api) - [Google Geocoding API](https://cohesivity.ai/offerings/google-geocoding-api) - [OpenAI API](https://cohesivity.ai/offerings/openai-api) - [AI Gateway](https://cohesivity.ai/offerings/ai-gateway) - [Deepgram API](https://cohesivity.ai/offerings/deepgram-api): server-side Token auth - [Exa API](https://cohesivity.ai/offerings/exa-api) - [Steel Browser](https://cohesivity.ai/offerings/steel-browser): available to every tenant - [Inbox](https://cohesivity.ai/offerings/inbox) - [Postgres](https://cohesivity.ai/offerings/postgres): public for all tenants - [Redis](https://cohesivity.ai/offerings/redis) - [Object Storage](https://cohesivity.ai/offerings/object-storage) - [Railway Hosting](https://cohesivity.ai/offerings/railway-hosting): primary public hosting with direct Cohesivity upload and Cohesivity-managed tier compute caps; no GitHub, CLI, or Railway credentials - [Cloudflare Workers](https://cohesivity.ai/offerings/cloudflare-workers) - [Social Login](https://cohesivity.ai/offerings/social-login): supports `?return_to=/path` on the login URL so apps resume deep links after OAuth - [Realtime](https://cohesivity.ai/offerings/realtime): receivers and history include both `action` and `event` with the same value, so publishing `{ "event": "message" }` delivers `{ "action": "message", "event": "message", ... }` - [Vector Database](https://cohesivity.ai/offerings/vector-database): profile-based shared indexes - [Domain Purchase](https://cohesivity.ai/offerings/domain): registered at Name.com on payment capture ### Feedback A feedback endpoint records the Cohesivity build experience: GET /api/feedback returns a structured prompt (what to include) POST /api/feedback submits feedback as { "feedback": "..." } Both require `Authorization: Bearer `. The GET response names the sections to include (GOAL, ISSUES, WHAT WORKED, TIME BREAKDOWN, DIRECTIVES, WISH). A later POST appends new text to the existing feedback document for this tenant; no need to repeat prior content.