# CWM API

Base URL: `https://cwm.socialhub.ai`. All endpoints below require
`Authorization: Bearer YOUR_CWM_API_KEY` and JSON request bodies.
Create a key in **Menu → Integrations → API keys**. Secrets are shown once and expire after 90 days.
Use server-side integrations; never embed a secret in a public browser application.

## Prepaid credits

USD 1 buys 10,000 credits. New registrations receive USD 5 (50,000 credits) once for
immediate trial use. The trial allowance is capped at 50,000 credits of cumulative
usage; remaining gift credits carry into paid service. The workspace owner manages
deposits and paid service activation in **Settings → Billing**.

One newly accepted valid behavioral event costs 1 credit; one member × objective
score costs 10 credits; an API response with actionable recommendations costs 50
credits. State responses can contain three scores. Published enterprise inference
also counts its returned objective, with an additional decision fee for valid
item/basket recommendations. The paid contract states whether scores within
decisions are included or charged separately. A synchronous batch has at most one
decision-response fee and sums successful scoring units.

Credit availability is checked before paid work, with enough credits reserved for
the maximum possible response. Actual successful quantities are settled afterward.
Import previews, duplicate behavioral events, failed/unavailable outputs and
out-of-distribution enterprise conclusions do not consume credits. Historical
reads and management operations have no usage charge. The platform subscription
and separately accepted brand services also debit the wallet at the quoted rates.

HTTP `402` can carry `INSUFFICIENT_CREDITS`, `TRIAL_CREDITS_EXHAUSTED` or
`SUBSCRIPTION_REQUIRED`. Stop automatic retries and have the owner fund/activate
service. Bulk refresh jobs pause with `INSUFFICIENT_CREDITS`; resume after funding
and any required service activation. Renewal failure pauses paid service without
accumulating unpaid monthly debt. Client-supplied quantities or payment flags
cannot add credits or override rates.

## Permissions and tenant boundaries

| Scope | Access |
|---|---|
| `predictions:read` | Standard analysis and inference with a published enterprise version |
| `decisions:read` | Decision/history queries |
| `decisions:write` | Record a decision or its outcome, using single calls |
| `cwm:data:read` | Import status and recent member ID mappings |
| `cwm:data:write` | Preview and confirm data imports |
| `cwm:training:read` | Training/alignment versions, reports and schedules |
| `cwm:training:write` | Upload, start, cancel, delete and manage incremental schedules |
| `cwm:training:review` | Approve/reject reports and publish/unpublish versions |

Existing keys keep their existing permissions. Create a new scoped key to enable management APIs.
The key must belong to an active workspace administrator. The server derives the workspace and
actor from that key. Do not pass tenant IDs, actor IDs or other identity overrides.
Samples, parameters, reports and schedules are isolated by tenant. These workflows never update
the shared standard model. Test-origin versions remain test-only.

## Single analysis

`GET /api/v1/capabilities` returns the key's authorized capabilities and their JSON input schemas.
`POST /api/v1/capabilities` accepts:

```json
{"name":"cwm_get_consumer_state","arguments":{"member_id":"CWM_MEMBER_UUID"}}
```

Response: `{"result": ...}`. Use the CWM UUID returned in import `mappings` or
`GET /api/v1/data`; REST member inputs use UUIDs, not external seed IDs.
Honor `envelope.status`, evidence and limitations. HTTP success means execution completed,
not that a reliable prediction exists. An `unavailable` result must not be interpreted as zero risk.
For paginated capabilities, use the returned `runId`/`hasMore` or `nextCursor` with the same
capability's `run_id` or `after_member_id` argument. Batch calls do not automatically drain pages.

## Batch analysis

`POST /api/v1/capabilities/batch` accepts 1–20 independent analysis requests:

```json
{"requests":[
  {"id":"customer-a","name":"cwm_get_consumer_state","arguments":{"member_id":"FIRST_MEMBER_UUID"}},
  {"id":"customer-b","name":"cwm_predict_purchase","arguments":{"member_id":"SECOND_MEMBER_UUID"}}
]}
```

IDs must be unique within a batch. Results preserve input order, with at most three concurrent
executions. The body limit is 256 KiB. Batch calls have an additional limit of 60 items/key/minute.
Recording decisions or feedback is not supported in batches. Analyses may save analysis history.

```json
{"total":2,"succeeded":1,"failed":1,"results":[
  {"id":"customer-a","ok":true,"status":200,"result":{"envelope":{"status":"degraded"}}},
  {"id":"customer-b","ok":false,"status":404,"error":{"code":"NOT_FOUND","message":"Consumer not found"}}
]}
```

HTTP 200: all calls completed. HTTP 207: one or more item errors, including when all fail.
Malformed batch envelopes return HTTP 400 before execution. Invalid arguments, denied capabilities
and missing members fail individually. Retry only failed items; IDs correlate responses and are
not persistent idempotency keys. After an item-level 429, wait one minute. Model-level `unavailable`
is still a completed call and appears inside `result`, separate from transport errors.

## Initial data alignment / import

`GET /api/v1/data` returns counts, recent batches and the latest 100 member mappings.
`POST /api/v1/data` first previews a CSV or JSON import:

```json
{"kind":"orders","format":"csv","content":"external_id,member_id,occurred_at,amount,currency,status\nO1,M1,2026-06-01,128.50,USD,completed\n","mapping":{}}
```

Kinds: `members`, `orders`, `events`, `order_lines`, `channel_receipts`.
Import members before their dependent records. Column mapping and validation match the upload UI.
Download templates from the workspace upload page. The preview returns `issues`, `token`,
`inserted`, `skipped`. If there are no issues, submit the **identical** body with
`"confirmed":true,"token":"PREVIEW_TOKEN"`. Tokens expire after 15 minutes and bind the
workspace, administrator and exact data. Imports append; existing IDs with different data fail.
Confirmed responses include member mappings. Keep those mappings in your application.

`PATCH /api/v1/data` with `{"activate":true}` explicitly enables the workspace's data-training
pipeline. Requires both data-write and training-write scopes. This is optional and is **not**
required for standard-model inference; importing data or activating a pipeline does not mean a
trained version has passed evaluation.

## Enterprise extension / post-training API

`GET /api/v1/training` returns the latest 30 versions, recent events and incremental schedules.
`POST /api/v1/training` accepts actions below. Jobs execute asynchronously in the worker;
poll GET for status/progress. Queueing a job does not mean it has completed.

### Upload samples

```json
{
  "action":"upload","name":"Customer return model v1","confirmed":true,
  "config":{"task":"return","origin":"test","horizon":30,"ruleVersion":"demo-v1","featureDefinition":"Features measured before the prediction cutoff; outcomes observed for the entire 30-day window."},
  "content":"CSV_FILE_CONTENT"
}
```

Tasks: `return` (returns), `item` (next item), `basket` (item combination), `tier` (membership
upgrade). Use `origin:"live"` only for genuine business samples. `ruleVersion` is required for tier
training. Use the training page's CSV templates: `sample_id,entity_id,as_of,label_end,target` plus
numeric `f_...` feature columns. Include enough independent entities and complete observation
windows to pass the same validation/evaluation gates as the UI. Upload is limited to 2 MiB of CSV
inside a 3 MiB JSON request. Upload returns `id`; identical data/configuration reuse the existing ID.

### Lifecycle

```json
{"action":"start","id":"VERSION_UUID"}
```

Other actions: `cancel`, `delete`, `reports`, `approve`, `reject`, `publish`, `unpublish`, `infer`.
Start/cancel/delete use training-write; reports use training-read; review/publication use
training-review; published inference uses predictions-read. Reports support `offset` (pages of 20).
Deletion is allowed only after stopping jobs and unpublishing the version.

```json
{"action":"reports","id":"VERSION_UUID","offset":0}
```

Each completed attempt produces a report. Read the current report, its `id` and `report_hash`,
then have an administrator review it. Submit their decision explicitly:

```json
{"action":"approve","id":"VERSION_UUID","reportId":"REPORT_UUID","reportHash":"EXACT_REPORT_HASH","note":"Reviewed the evaluation and business applicability.","acknowledged":true}
```

Use `reject` to decline. A stale report, changed artifact, failed evaluation or already-handled
review cannot be approved. Approval alone does not publish:

```json
{"action":"publish","id":"VERSION_UUID"}
```

Inference uses an approved, published tenant version:

```json
{"action":"infer","id":"VERSION_UUID","features":{"f_pattern":2,"f_activity":-1}}
```

Supply the exact features used during training. Results include business conclusions, recommended
actions, scores and applicability limits. Scores are not calibrated probabilities or accuracy.

### Incremental schedules

Create from an evaluated version:

```json
{"action":"schedule_create","baseId":"VERSION_UUID","enabled":true,
 "cadence":{"frequency":"weekly","time":"03:00","timezone":"America/Los_Angeles","weekday":1,"monthDay":1,"minNewSamples":100}}
```

Frequency: `daily`, `weekly`, `monthly`. Supply all cadence fields; `weekday` is ISO 1–7 and
`monthDay` is 1–31. Upload new mature samples with:

```json
{"action":"schedule_append","id":"SCHEDULE_UUID","content":"INCREMENTAL_CSV_CONTENT","confirmed":true}
```

`schedule_update` takes `id`, `cadence`, `enabled`. `schedule_pause`, `schedule_resume`,
`schedule_delete`, `schedule_check` take `id`. GET lists next-run times, sample counts and logs.
The worker queues a new version only when the incremental threshold is met. Every new run needs
a new report review and separate publication. Pausing a schedule does not cancel an existing job.

## Standard-model parameter alignment

`GET /api/v1/alignment`: latest 20 alignment versions and recent events.
`POST /api/v1/alignment`: `upload`, `start`, `cancel`, `reports`, `approve`, `reject`, `publish`,
`unpublish`, `delete`, `infer`. Permissions and review gates match the training API.

Upload body:

```json
{"action":"upload","name":"Purchase alignment v1","confirmed":true,
 "config":{"target":"purchase","origin":"test","horizon":30,"ruleVersion":"purchase-v1","definition":"A completed and non-refunded purchase during the 30-day observation window."},
 "content":"ALIGNMENT_CSV_CONTENT"}
```

Targets: `purchase`, `churn`, `abandonment`. CSV headers:
`sample_id,entity_id,as_of,label_end,target,events`. Supply 500–2,000 independent samples.
`events` is a CSV-escaped JSON array of 3–512 ordered `[unix_seconds,"buy"|"pv",0,0]` events,
strictly before `as_of`. Download the parameter-alignment CSV template from the training page.
The worker calls the standard model and learns only the enterprise adapter. Version fingerprints
and evaluation/report approvals must still match when publishing.

```json
{"action":"infer","id":"ALIGNMENT_VERSION_UUID","member_id":"CWM_MEMBER_UUID"}
```

This explicit alignment trial also accepts an imported external member ID. Published live alignment
is applied by supported standard analyses when applicable; test versions require explicit trials.
Incremental schedules currently apply to the four extension models; alignment uses explicit jobs.

## Errors and retries

400: malformed input; 401: missing/invalid/expired key; 403: permission or credential boundary;
404: resource absent from this tenant; 409: incompatible state or stale report; 413: upload too
large; 429: rate limit; 5xx: service failure. Do not retry approvals or recording writes blindly:
first fetch current state. Never substitute a different tenant ID to resolve a missing record.
# Large asynchronous member refresh

Use this channel for whole-workspace state refreshes, including workloads with millions of members. `/api/v1/capabilities/batch` remains a small synchronous channel (20 calls). Do not submit millions of member IDs or keep an HTTP request open while processing.

Authorize `cwm:bulk:write` to manage jobs and `cwm:bulk:read` to query jobs and saved states. Existing analysis keys do not inherit these permissions. Workspace and actor identity always come from the key. The website's **Integrations → Connection methods → Large refresh** page manages the same jobs.

```http
POST /api/v1/bulk-jobs
Authorization: Bearer YOUR_KEY
Content-Type: application/json

{"action":"create","name":"Weekly member refresh","idempotency_key":"refresh-2026-09-30-001"}
```

Returns HTTP 202 with `{ "id": "JOB_UUID", "reused": false }`. Retry an uncertain submission with the same key and name: it returns the same job. Reusing the key with a different name returns 409. Only one queued, running or paused job may exist per workspace.

```text
GET /api/v1/bulk-jobs
GET /api/v1/bulk-jobs?id=JOB_UUID
GET /api/v1/bulk-jobs?id=JOB_UUID&view=results&limit=100
GET /api/v1/bulk-jobs?id=JOB_UUID&view=results&status=failed&limit=100
GET /api/v1/member-states?limit=100
GET /api/v1/member-states?member_id=MEMBER_UUID
```

Result/state pages return `nextCursor`. Pass it as `after` on the next request, preserving other filters; stop when it is null. Maximum page size: 1,000. Job listing returns the 20 most recent jobs. During a running job, results may arrive behind a cursor; read the final complete result set after completion. Current-state pages are a live view, not an atomic snapshot. Inspect each state's `job_id`, `source_cutoff` and `updated_at` to detect older retained results after a failed refresh.

Job status includes `processed`, `succeeded`, `unavailable`, `failed`, and `attempts`. `processed` counts unique attempted members, not the total workspace population. The eligible population is scanned progressively; no costly total count runs on submission. A `completed` job can include unavailable members; it does not mean every member had enough data for inference. Temporary failures are retried automatically, up to three attempts. Repeated model unavailability pauses the job with `pause_reason=MODEL_SERVICE_UNAVAILABLE`.

```json
{"action":"pause","id":"JOB_UUID"}
```

Send the same shape with `resume`, `cancel` or `retry`. Resume continues a paused scan; retry is available for `completed_with_errors` and processes failed members only. Missing data is not automatically retried: import the necessary facts and create a new full refresh. Cancel stops future writes; already saved states remain. Pausing fences in-flight writes, though a model request already sent may finish.

Refresh output uses the same standard `cwm_get_consumer_state` inference as single-member analysis: observed facts plus purchase, long-gap and cart-removal reference signals. These are not calibrated probabilities or enterprise-trained churn labels. This channel does not train models, apply enterprise alignment heads, execute campaigns, or overwrite imported business facts. It writes a separate queryable latest-state store.

Members must be active, unrestricted and created by the job's `source_cutoff`. Events are filtered by occurrence time up to that cutoff. Facts are read when each member runs, so late-arriving/backdated records or corrections are not an immutable submission-time snapshot. Privacy restrictions are rechecked before inference, persistence and result access. All records, leases and queries are tenant-scoped.

Job member details expire seven days after job completion/cancellation (expired reads return 410); latest member states and job summaries remain. Failed inference preserves the previous latest state rather than replacing it with invented zero scores. A retry of an older job cannot overwrite a state from a newer cutoff.

## Bulk operations and capacity

Apply `2026_11_25_cwm_bulk_refresh.sql` and `2026_11_26_cwm_bulk_privacy_index.sql` to public and every tenant schema, then deploy both web and worker. The worker uses 16 disjoint UUID shards, 50-member cursor pages, durable leases and per-result checkpoints. A lost worker's lease can be reclaimed after 120 seconds. No `OFFSET` scan or all-member in-memory list is used.

`CWM_BULK_RPS` controls the fleet-wide Redis budget for bulk model calls (default 2/second, allowed 1–100). Redis failure stops new bulk calls. Each worker runs at most two simultaneous calls. Extra worker replicas can claim different shards. `CWM_BULK_ONLY=true` runs a dedicated bulk worker, separate from scheduled training/state work. Actual throughput is constrained by model capacity, database I/O, worker concurrency and the shared model dependency; the rate budget is a ceiling, not a guarantee or resource reservation.

Ten million members require substantial compute and storage. At a sustained 100 members/second, inference alone takes about 27.8 hours; at the conservative default ceiling of 2/second it takes about 57.9 days, before overhead or retries. Benchmark representative inputs and size the fleet/database before committing a completion SLA. This implementation has not been certified by a ten-million-member load test. Latest states plus retained per-job results grow proportionally to member count; provision storage and monitor job age, throughput, failures and retention cleanup.
