# Package: the submission pack, as a set of draft exhibits

You have priced an object and backtested a structure cut from it. This page is
where those runs become one document you can hand to a person: an exposure
summary, the technical price with its complete assumption set, the backtest
behind it, and a draft slip laying the terms out the way a reader expects to
meet them — assembled, addressable, and watermarked **DRAFT** on every page.

Two routes:

| Route | What it does |
| --- | --- |
| `POST /objects/{object_id}/package` | Assemble the pack for an object. A job, like every verb |
| `GET /packages/{package_id}` | The assembled pack, exactly as it was assembled |

Read [the quickstart](quickstart.md) first for the two values you export
(`CEDE_BASE_URL`, `CEDE_API_KEY`) and how to get a key from `POST /signup`.

## The one thing to know before you call it

**Package computes nothing.** It assembles documents that already exist: every
exhibit in a pack is a run recorded in the object's own
`provenance.model_runs`, reproduced as it was computed. So the order is fixed —

    POST /objects            (or POST /ingest, then PATCH /objects/{id})
    POST /objects/{id}/price                     ← the pack needs this
    POST /objects/{id}/structures                ┐ optional: gives the pack
    POST /structures/{id}/backtest               ┘ its backtest exhibit
    POST /objects/{id}/package

— and an object with no price run behind it is refused rather than priced on
the way past:

```json
{ "error": {
    "status": 422,
    "code": "unpackageable_object",
    "message": "this object has no price run in its provenance, so there is no pricing exhibit to assemble and no pack to hand you. Run POST /objects/{id}/price first, then package it" } }
```

That refusal arrives on the request, before a job exists, so you never poll to
discover it. The reason for the rule is worth a sentence: a number computed
while a pack was being built would be a number nobody reviewed, and two packs
of one object would disagree the day the pinned data moved. What is in a pack
was seen, and priced, before it went in.

The same rule reaches the **draft slip**, which is populated from the object
rather than from a run. If the object does not carry a block the slip lays out
— the parties, the period, the jurisdiction, the financial structure, the
trigger — the pack is refused with those blocks named, all of them in one
message, so one `PATCH` fixes it:

```json
{ "error": {
    "status": 422,
    "code": "unpackageable_object",
    "message": "this object does not carry what the draft slip in the pack is populated from: the parties (`counterparties`), the jurisdiction (`jurisdiction`). The slip restates the object and computes nothing, so each missing block would be a blank section rather than a draft: PATCH /objects/{id} with the missing block(s), then package it" } }
```

The technical premium is named the same way and fixed differently: it is the
one line on the slip a *run* writes, so the message asks you to price rather
than to patch. You can meet that one after a successful price run — changing
a pricing input with `PATCH /objects/{id}` takes the stale premium off the
object, and the slip will not draft around the hole.

---

## Assembling one

<!-- cede:runnable -->

```bash
set -euo pipefail

: "${CEDE_BASE_URL:?export CEDE_BASE_URL first — see the quickstart}"
: "${CEDE_API_KEY:?export CEDE_API_KEY first — see the quickstart}"

auth="Authorization: Bearer $CEDE_API_KEY"

field() {
  python3 -c 'import json, sys
document = json.load(sys.stdin)
for step in sys.argv[1].split("."):
    document = document[int(step)] if step.isdigit() else document[step]
print(document)' "$1"
}

await_job() {
  state=unknown
  for _ in $(seq 1 300); do
    curl -sS --fail-with-body -H "$auth" "$CEDE_BASE_URL/jobs/$1" > job.json
    state=$(field status < job.json)
    case "$state" in succeeded|failed) break ;; esac
    sleep 0.2
  done
  if [ "$state" != succeeded ]; then
    printf 'job %s did not succeed: %s\n' "$1" "$(cat job.json)" >&2
    exit 1
  fi
}

# 1 — A complete parametric object: exposure, peril with the box, limit and
#     attachment, and the index that decides the payout.
cat > package-object.json <<'JSON'
{
  "schema_version": "0.1.0",
  "status": "analysed",
  "exposure": {
    "kind": "location_schedule",
    "currency": "JPY",
    "locations": [
      { "ref": "LOC-0001",
        "address_as_given": "2-16-1 Konan, Minato-ku, Tokyo 108-0075",
        "latitude": 35.6284, "longitude": 139.7387,
        "geocode": { "resolution": "rooftop", "confidence": 0.96 },
        "occupancy": "warehouse", "construction": "reinforced_concrete",
        "year_built": 2011,
        "values": { "building": { "amount": 8400000000, "currency": "JPY" } } }
    ],
    "source_fidelity": { "unmapped_columns": [], "guessed_units": [], "ambiguous_rows": [] }
  },
  "peril": {
    "code": "earthquake",
    "region": {
      "description": "Kanto cat-in-a-box: 34.9-36.2N, 139.0-140.6E.",
      "bounding_geometry": {
        "type": "Polygon",
        "coordinates": [[[139.0, 34.9], [140.6, 34.9], [140.6, 36.2],
                         [139.0, 36.2], [139.0, 34.9]]]
      }
    }
  },
  "financial_structure": {
    "limit": { "amount": 2000000000, "currency": "JPY" },
    "attachment": { "value": 6.0, "unit": "M", "index_ref": "kanto-eq-box-magnitude" }
  },
  "trigger": {
    "type": "parametric_cat_in_a_box",
    "index": {
      "name": "kanto-eq-box-magnitude",
      "version": "1.0.0",
      "description": "Largest catalogue magnitude inside the Kanto box, shallower than the depth threshold.",
      "measurement": { "variable": "catalogue_preferred_magnitude", "unit": "M", "statistic": "max" },
      "aggregation_window": { "duration": "PT72H", "alignment": "event" },
      "thresholds": [
        { "label": "attachment", "level": 6.0, "unit": "M" },
        { "label": "exhaustion", "level": 7.0, "unit": "M" },
        { "label": "max_focal_depth", "level": 100, "unit": "km" }
      ],
      "payout_function": {
        "type": "step",
        "points": [ { "level": 6.0, "payout_ratio": 0.25 },
                    { "level": 6.5, "payout_ratio": 0.5 },
                    { "level": 7.0, "payout_ratio": 1 } ],
        "maximum_payout_ratio": 1
      }
    },
    "data_sources": [
      { "id": "usgs-eq-kanto",
        "name": "USGS ANSS Comprehensive Earthquake Catalog - Kanto extract",
        "kind": "quake_catalogue",
        "version": "2026-08-10", "vintage": "2026-08-10" }
    ]
  },
  "period": {
    "inception": "2026-04-01T00:00:00+09:00",
    "expiry": "2027-04-01T00:00:00+09:00",
    "timezone": "Asia/Tokyo"
  },
  "jurisdiction": {
    "governing_law": "GB-ENG",
    "territories": ["JP-13", "JP-12"]
  },
  "counterparties": [
    { "role": "protection_buyer", "name": "Kanto Logistics Captive Ltd" },
    { "role": "calculation_agent", "name": "Meridian Analytics Services Ltd" }
  ]
}
JSON

object_id=$(curl -sS --fail-with-body -H "$auth" \
  -H "Content-Type: application/json" \
  --data-binary @package-object.json \
  "$CEDE_BASE_URL/objects" | field id)
echo "object: $object_id"

# 2 — Price it. The pack needs this run; without it, packaging is refused.
price_job=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL/objects/$object_id/price" | field id)
await_job "$price_job"

# 3 — Cut a structure and backtest it. Optional — but the run is what puts a
#     backtest exhibit in the pack, and the object's provenance is where the
#     pack finds it.
structure=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL/objects/$object_id/structures")
backtest_path=$(printf '%s' "$structure" | field links.backtest)
backtest_job=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL$backtest_path" | field id)
await_job "$backtest_job"

# 4 — Assemble the pack. A job, like every verb — and the only one that reads
#     rather than computes.
package_job=$(curl -sS --fail-with-body -X POST -H "$auth" \
  "$CEDE_BASE_URL/objects/$object_id/package" | field id)
await_job "$package_job"
package_path=$(field result.package.links.self < job.json)
echo "pack: $package_path"

# 5 — Read it back at its own address. This is the document you hand on.
curl -sS --fail-with-body -H "$auth" "$CEDE_BASE_URL$package_path" > pack.json

python3 -c 'import json
pack = json.load(open("pack.json"))
print("status:   ", pack["status"])
print("watermark:", pack["watermark"])
print("subject:  ", pack["subject"]["object_id"], pack["subject"].get("peril", ""))
print()
print("assembled from the runs this object already carries:")
for run in pack["assembled_from"]:
    print("  ", run["verb"], run["run_id"], "-", run["model"], run["model_version"])
print()
for name, document in pack["documents"].items():
    print(name, "->", document["title"])
summary = pack["documents"]["exposure_summary"]["exposure"]
print()
print("exposure: ", summary["kind"], summary["location_count"], "location(s)")
print("  unmapped columns:", summary["source_fidelity"]["unmapped_columns"],
      "guessed units:", summary["source_fidelity"]["guessed_units"],
      "ambiguous rows:", summary["source_fidelity"]["ambiguous_rows"])
price = pack["documents"]["pricing_exhibit"]["price"]
print("price:    ", price["technical_price"]["basis"],
      price["technical_price"]["amount"], price["technical_price"]["currency"],
      "- rate on line", price["technical_price"]["rate_on_line"])
print("  assumptions carried:", ", ".join(sorted(price["assumptions"])))
exhibit = pack["documents"].get("backtest_exhibit")
if exhibit:
    statistics = exhibit["backtest"]["statistics"]
    print("backtest: ", statistics["triggering_years"], "triggering year(s) of",
          statistics["years"])
print()
slip = pack["documents"]["draft_slip"]
print(slip["title"])
print(slip["watermark"])
terms = slip["slip"]
for party in terms["parties"]:
    print("  party:  ", party["role"], "-", party["name"])
print("  period: ", terms["period"]["inception"], "to", terms["period"]["expiry"],
      "(" + terms["period"]["timezone"] + ")")
print("  law:    ", terms["jurisdiction"]["governing_law"],
      "- territories", ", ".join(terms["jurisdiction"].get("territories", [])))
limit = terms["financial_structure"]["limit"]
print("  limit:  ", limit["amount"], limit["currency"])
print("  trigger:", terms["trigger"]["type"],
      terms["trigger"].get("index", {}).get("name", ""))
for layer in terms["financial_structure"].get("layers", []):
    print("  layer:  ", layer.get("name", ""), layer["limit"]["amount"],
          layer["limit"]["currency"], "share", layer["share"])
slip_premium = terms["technical_premium"]
print("  premium:", slip_premium["basis"], slip_premium["amount"],
      slip_premium["currency"], "- assumptions", slip_premium["assumptions_ref"])
print("  ", slip_premium["disclosure"])
for statement in terms["statements"]:
    print("  -", statement)
print()
for limitation in pack["limitations"]:
    print("*", limitation)
print()
print(pack["disclosure"])'
```

**`POST /objects/{object_id}/package`** answers `202` with a job, like every
verb. Its result carries the whole pack, and the pack's `links.self` is the
address it is served at afterwards.

**`GET /packages/{package_id}`** serves what was assembled, byte for byte,
forever. It is not a live view: run Price again and the stored pack does not
move, because a document somebody has already taken into their own process
must not change underneath them. Assemble a second pack when you want the
newer run — assembling is free.

---

## What is in a pack

| Document | What it holds |
| --- | --- |
| `exposure_summary` | What is at risk, off the object: kind, location count, values, and Ingest's honesty channel as counts — how many columns went unmapped, units were guessed, rows were ambiguous |
| `pricing_exhibit` | The price response of the run the object's provenance names, **whole**: the technical premium, the expected loss, the loadings and the complete assumption set that produced them |
| `backtest_exhibit` | The backtest artifact, when a backtest of a structure cut from this object is recorded. Absent otherwise — never an empty section, and never a run made up at assembly time |
| `draft_slip` | The terms of the object on one page: the parties, the period, the jurisdiction, the financial structure with its layers, the trigger, and the technical premium restated as analytical output |

`assembled_from` names every run that went in, in the same shape the object's
`provenance.model_runs` records it, so you can check a pack against the object
it came from without trusting the pack.

The honesty channel travels on purpose. A submission is exactly where "three
columns in the schedule were not understood" most needs to survive the trip;
the per-row detail stays on the object, one call away.

## The draft slip

The slip is the document in a pack that looks most like paper, so it is the
one worth being most precise about. Here is what it is.

**It is a restatement, and only that.** Every line of it is read off the
object: `parties` is the object's `counterparties` array as recorded,
`period`, `jurisdiction`, `financial_structure` (limit, attachment, layers
where Structure produced any, reinstatements where the object carries them)
and `trigger` — the parametric index definition whole, or the wording
reference, whichever branch the object populates. Nothing on the slip is
computed at assembly time, and where the slip and the object disagree, the
object is the record. The slip says that itself, in `statements`.

**The premium is on it, labelled.** `technical_premium` carries `basis:
"technical"`, the amount and currency, `assumptions_ref` — the pointer to the
assumption set that produced it, which the pricing exhibit in the same pack
carries in full — and `price_run_ref`, naming the run so you can match the
number to the exhibit without doing it by eye. It also carries its own
`disclosure`, a fixed sentence in the published schema, because a premium on a
slip is the number in this pack most likely to be misread as something someone
stands ready to transact at. It is not. It is an actuarial estimate under the
assumptions it points at, and nobody has agreed to it.

**The parties create nothing.** Recording a party on a risk object is
descriptive — it creates no obligation and no relationship through Craton, and
Craton verifies no identifier on any of them. Reproducing those names on a slip
does not change that.

**There is no other side to it.** No field on the slip, and no field its
published schema allows, records a signature, an acceptance, a counterparty's
assent or an execution — and none will be added. The slip cannot be executed
here at all: it carries the same watermark as every other document in the
pack, and a person who takes it forward does so entirely in their own
placement process, with their own paper.

If the object cannot fill a section, the pack is refused and the section is
named — see the refusal above. A slip with a blank governing law is worse than
no slip, because it looks finished.

## What a pack is, and is not

* **A draft, and it says so on every page.** The pack carries a watermark, and
  so does each document inside it — because an exhibit is the thing you lift
  out and send on alone, and a watermark on the wrapper alone would come off in
  the first email. The sentence is fixed in the published schema, so no
  conforming pack has ever said anything else.
* **Execution ready in exactly one sense.** You can take these documents into
  your own placement process and execute them there. Craton is not a party to
  anything in them, is not a risk carrier and not an intermediary, and holds no
  signature, acceptance or execution mechanism at all — there is no route here
  that records anyone agreeing to anything, and there will not be one.
* **A record of runs, not a new opinion.** Every figure came from a run you can
  address by its own id, with its own data vintages and its own assumptions.
* **Technical prices only.** The pricing exhibit is an actuarial estimate under
  the assumptions it carries, and it says so in its own disclosure.
* **History, not a projection.** The backtest exhibit is what the record did,
  under the data the run read.
* **Free.** Craton bills per object and per model run; assembling a pack is
  neither, and everything in it was already billed by the run that produced it.
  `GET /usage` will not show a package line.

The published schema is `package.v0.schema.json`, listed in the API contract
document with every other response shape — validate a pack against it while
holding no Craton code. It declares `0.2.0` since the draft slip landed: a pack
assembled before that carries `0.1.0` and three documents, and is served back
exactly as it was assembled, because a pack is a record and never a live view.

## Where to go next

* [Price](price.md) — the run the pricing exhibit reproduces.
* [Backtest](backtest.md) — the run behind the backtest exhibit.
* [Errors](errors.md) — every refusal this API reports, `unpackageable_object`
  included.
