Get a free API key

This page as Markdown, byte for byte: /package.md

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 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:

{ "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:

{ "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#

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#

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#