CRATON
Get a free API key

Articles · Hands-on

Code a schedule of values with one API call.

Rows of text in, OED codes out, and a plain answer about how sure the model is.

September 25, 2026 · Craton

Every schedule of values describes its buildings in the words of whoever typed it. A catastrophe model wants two numbers for each one: an OED OccupancyCode and an OED ConstructionCode. This walk sends three real rows to Craton’s POST /exposure-codes, reads what comes back, and turns it into a file a model can take.

1. Get a key

Keys are free and self-service. One call, no form:

Get a sandbox key
curl -sS -X POST https://api.oncraton.com/signup \
  -H 'Content-Type: application/json' \
  -d '{"label": "sov coding"}'

The answer carries api_key.secret. Export it as CRATON_KEY for the next step.

2. Send the rows

One building a row, as written. Three rows from the hand-written hard cases in the benchmark we ran on the same model: a big box store, a ranch house, and a fire station whose walls and roof pull in two directions.

POST /exposure-codes
cat > rows.json <<'JSON'
{"rows": [
  "Big box retail w/ small office mezzanine - tilt-up - 2003",
  "Frame w/ brick veneer, 1-story ranch home",
  "Fire station w/ attached community meeting room, CMU bearing walls, steel joist roof"
]}
JSON

curl -sS https://api.oncraton.com/exposure-codes \
  -H "Authorization: Bearer $CRATON_KEY" \
  -H 'Content-Type: application/json' \
  --data @rows.json -o answer.json

That is the whole request. You never send a question or a prompt: the questions each row is asked are fixed on Craton’s side and named by digest in every answer. A body with anything but rows is refused.

3. Read the answer

Here is what came back for those rows, trimmed to the parts worth reading. It is a real answer: the three replies were recorded from Jev through Vercel’s AI Gateway on September 25, 2026, and replayed through the same route you just called. The gates are the ones the benchmark chose: 0.95 for occupancy, 0.85 for construction.

200, trimmed
{
  "model": {"name": "typesafe-ai/jev", "served_by": ["typesafe-ai"], "via": "vercel-ai-gateway"},
  "gate": {"OccupancyCode": 0.95, "ConstructionCode": 0.85},
  "counts": {"coded": 2, "needs_review": 1, "rows": 3, "unanswered": 0},
  "rows": [
    {"row": 1, "status": "coded", "fields": {
      "OccupancyCode":    {"code": 1101, "name": "Commercial, Retail trade", "confidence": 1.0, "why": "cleared"},
      "ConstructionCode": {"code": 5155, "name": "Concrete, Tilt-up", "confidence": 1.0, "why": "cleared"}}},
    {"row": 2, "status": "coded", "fields": {
      "OccupancyCode":    {"code": 1051, "name": "Residential, Permanent dwelling: single-family", "confidence": 1.0, "why": "cleared"},
      "ConstructionCode": {"code": 5052, "name": "Wood, Masonry veneer", "confidence": 0.98, "why": "cleared"}}},
    {"row": 3, "status": "needs_review", "fields": {
      "OccupancyCode":    {"code": 1220, "name": "Government, Emergency services", "confidence": 1.0, "why": "cleared"},
      "ConstructionCode": {"code": null, "proposed": 5100, "name": "Masonry, Masonry",
                           "confidence": 0.75, "why": "below_gate",
                           "alternatives": [{"code": 5105, "name": "Masonry, Reinforced masonry", "probability": 0.14}, {"code": 5108, "name": "Masonry, Joisted masonry", "probability": 0.08}]}}}
  ]
}

Three things to notice.

  • code is the only field to write into your file. It is set only when the model’s confidence is at or above the gate for that field. Below the gate it is null, and the suggestion sits in proposed.
  • Row 3 is honest about itself. CMU bearing walls under a steel joist roof is masonry, but whether it counts as plain masonry or the non-combustible class depends on the roof deck, and the row does not say. The model leaned toward plain masonry at 0.75, below the construction gate of 0.85, and named reinforced masonry as the runner-up. That is a call for a person, and the answer says so instead of picking one.
  • Each field stands alone. Row 3’s occupancy, a fire station, cleared at 1.0 and is coded; only its construction waits for you.

The why member tells you what happened to each field: cleared, below_gate, unknown (the row simply does not say, so OED’s Unknown code is never written for you), unrecognized_answer, or no_answer.

4. Turn it into a file

OED location files are CSV. This writes one row per building, leaves a field blank wherever it did not clear, and keeps the status beside it so the rows to check are easy to filter.

answer.json to oed-locations.csv
import csv, json

rows = json.load(open("rows.json"))["rows"]
answer = json.load(open("answer.json"))

with open("oed-locations.csv", "w", newline="") as out:
    writer = csv.writer(out)
    writer.writerow(["LocNumber", "Text", "OccupancyCode", "ConstructionCode", "Status"])
    for coded, text in zip(answer["rows"], rows):
        fields = coded["fields"]
        writer.writerow([
            coded["row"],
            text,
            fields["OccupancyCode"]["code"] or "",
            fields["ConstructionCode"]["code"] or "",
            coded["status"],
        ])

The SOV coder on this site does the same thing in the browser, with a place to accept a suggestion or pick an alternative before you download.

5. Send a real schedule

  • Batch it. A request takes up to 50 rows of up to 1,000 characters. The model provider paces how fast Craton may ask it, so ten or so rows a request comes back quickest; send the next batch when one returns.
  • Retry only what was missed. A row the provider did not get to comes back unanswered. Send just those rows again. If none were answered, the route says 503 with a Retry-After.
  • A table reads better with its headers. For a spreadsheet, send each row as Header: value; Header: value, leaving out empty cells and money columns. That is the shape our benchmark tested.
  • Only building words. The row text goes to an AI provider for coding. Send what describes the building, never a person’s name.

Where the rows go. Each row is sent to Vercel’s AI Gateway and on to the service running TypeSafe’s Jev model, for that request only. The gateway’s listing says the providers do not train on it. Craton does not store the rows or log them.

The full reference, with every field and limit, is on the exposure coding page of the docs. How the gate was chosen is in Confidence gates for exposure data.