TourBook

TourBook import format

TourBook brings in tours, destinations, sector topos and challenge lists from one documented file format — an import file, a JSON file named *.tourbook.json — and this page describes it completely. Whatever you kept your tours in (a spreadsheet, another app's export, a list of summits or a guidebook's topo), it becomes such a file outside the app: by a spreadsheet formula, a small script, or an AI given this page and the data. The app then imports the file through the same path its tour form saves through, so an imported tour is exactly what the same tour typed into the form would be.

This page is written for whoever produces the file. If you are an AI given this page and a spreadsheet: read the whole page, map every column you can to a field below, never invent a value the spreadsheet does not hold, and write one JSON file that passes the JSON Schema at the end of this page.

Workflow

Importing the same file again is safe: a tour is skipped when the logbook already holds an active tour on the same day with the same name (case and spacing ignored). Fix a refused tour in the file and import the whole file again; what came in the first time is not doubled.

File

An import file is one JSON object, UTF-8, of at most 32 MB:

{
  "format": "tourbook-import",
  "version": 1,
  "tours": []
}

Every object in the format has a fixed set of fields, listed below. A field that is not listed is refused, never ignored — a typo such as "gain" for "gainM" is reported rather than silently dropped. A field that is optional may be left out or set to null; both mean "not recorded".

Nothing is guessed. A value the app does not know — an unknown sport, a grade a scale does not have, a field the sport does not record — refuses its tour with a precise message. Map what you are sure of; leave out the rest, or put it into the tour's description.

Fields

File

FieldTypeRequiredMeaning
format"tourbook-import"yesNames the format.
version1yesThe version of this page. A file of another version is refused.
toursarray of toursone of fiveThe tours, in any order.
toposarray of toposone of fiveSector topos: routes without a tour (see Capturing a sector topo).
routesarray of topo routesone of fiveOnly on a sector's screen: routes into that sector, without naming it.
locationsarray of locationsone of fiveDestinations without a tour: peaks, places, areas, sectors (see Capturing destinations).
challengeListsarray of challenge listsone of fiveLists of summits, huts or passes to tick off (see Capturing challenge lists).
$schemastringnoIgnored — room for a link to the schema.

A file holds tours, topos, destinations, challenge lists, or several — at least one of the five lists. On a sector's or area's screen it holds neither tours nor destinations nor challenge lists.

Tour

One outing on one day. A multi-day trip is several tours (one per day) in one collection.

FieldTypeRequiredMeaning
date"YYYY-MM-DD"yesThe day, a real calendar date (2024-07-14).
namestringyesThe tour's title ("Piz Palü East summit"). Together with the date it identifies the tour for re-runs.
descriptionstringnoFree text: notes, remarks, anything without a field of its own.
regionregionnoWhere the tour was: country, state, and/or a group of your own.
collectionstringnoThe collection the tour is filed under (a trip, a season, a project). Created when no collection of that name exists.
partnersarray of stringsnoWho was there, one name each (["Anna", "Beat Muster"]). A name the logbook knows is that person; a new name creates one.
tagsarray of stringsnoTags, by name; new ones are created.
segmentsarray of segments, at least oneyesThe sports of the day in order — a hike up and a flight down are two segments.

Region

At least one of country, state and group. The country and state are the administrative regions the app files tours under; the group is a region of your own — a valley, a range, a ski area ("Saastal", "Berner Oberland").

Give the ISO 3166 codes beside the names whenever you know them — "countryCode": "CH", "stateCode": "CH-VS". A country or state is identified by its code: with it, "Wallis", "Valais" and "Vallese" are the one canton the logbook and the installed region packs already know, and the name only labels a region the logbook does not hold yet. Without a code the name decides — use the names the logbook already uses ("Schweiz", "Wallis").

FieldTypeRequiredMeaning
countrystringnoThe country ("Schweiz").
countryCode"CH"noThe country's ISO 3166-1 alpha-2 code, in capitals; only beside country.
statestringnoThe state, canton or province inside it ("Wallis").
stateCode"CH-VS"noThe state's ISO 3166-2 code, in capitals; only beside state, and beginning with the countryCode when both are given.
groupstringnoA group of your own; created when none of that name exists.

A tour's region also decides which peaks, places, areas and sectors its names mean (see Finding known places): two peaks called "Schwarzhorn" in different states stay two peaks. Give the region whenever the spreadsheet says where a tour was.

Segment

One sport within the tour. Which fields a segment may carry depends on its sport — see Sports; a field the sport does not record is refused.

FieldTypeRequiredMeaning
sportsport keyyesOne key from Sports ("hiking", "climbing", …).
namestringnoA label for the segment ("Approach", "Flight down").
distanceKmnumber from 0 to 1,000noDistance in kilometres (12.5).
gainMnumber from 0 to 15,000noHeight gained in metres (1100).
lossMnumber from 0 to 15,000noHeight lost in metres.
duration"h:mm", at most "48:00"noTime taken, hours and minutes ("5:30"; "5:30:15" with seconds). A decimal such as 5.5 is refused — write "5:30". A longer outing is several tours.
gradegradesnoThe day's own difficulty, only on the sports marked segment grade ({ "hiking": "T3" }). Technical sports grade their routes instead.
peaksarray of peaksnoSummits of this segment.
placesarray of placesnoHuts, passes, trailheads, villages.
routesarray of routesnoThe routes climbed, skied or walked — with their grades.

Peak

FieldTypeRequiredMeaning
namestringyesThe summit's name ("Piz Palü").
latnumbernoLatitude in degrees (WGS84), with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.
outcome"reached" or "turned_back"noWhether the summit was reached. Left out, the routes at the peak decide (a route turned back from makes the peak "turned back"), else "reached".

A peak the logbook or an installed region pack already knows is that peak (see Finding known places); its position and height stay its own. The file's position and height are written only on a peak new to both — and the position is what tells two summits of one name apart.

Place

FieldTypeRequiredMeaning
namestringyesThe place's name ("Diavolezza", "Cabane de Moiry").
latnumbernoLatitude in degrees, with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.

Route

A route visited on the segment: where it lies, its grade, and how it went. Where it lies is exactly one of peak, place or sector (a sector with its area if known, a bouldering block with its sector and boulder), of a kind the sport hangs routes off (routes on in Sports).

FieldTypeRequiredMeaning
namestringyesThe route's name ("Normal route", "Motörhead").
peakstringone of threeThe summit the route leads to.
placestringone of threeA place the route starts from or leads to (a gorge, a ski area).
areastringnoThe climbing area a sector lies in ("Gadmen").
sectorstringone of threeThe crag sector ("Wendenstöcke"); for bouldering the boulder sector.
boulderstringboulderingThe block a problem is on, inside its sector.
gradesgradesnoThe route's grade per scale ({ "alpine": "ZS", "uiaa": "III" }).
pitchesarray of pitchesnoPitch by pitch, on sports with pitches.
stylestylenoHow it was climbed (see Visit answers), on the style sports.
rolerolenoLed, followed or alternated, on the rope sports.
attemptswhole number from 1 to 999noGoes needed, on the style sports (a project that took 12 tries).
outcomeoutcomenoReached or turned back, on the outcome sports.

A route already in the logbook at that location, with that name and sport, is that route: the visit is added to it and its grades and pitches stay as the logbook has them. A new route is created from the file's grades and pitches. A sector with its area is found by name inside that area (the area itself by name in the tour's region); a sector named without its area is the logbook's sector of that name and discipline in the tour's region — or, without a region, the only one of that name anywhere — and else a new sector without an area. Give the area whenever the spreadsheet has it.

Left out, the visit answers take the form's defaults: led, onsight on a route never climbed before (flash for a boulder), redpoint on one tried but not sent, repeat on one sent before; reached on the outcome sports.

Pitch

On sports with pitches. For a new route the pitches are its topo, in order; for a route already in the logbook the file must list as many pitches as the logbook holds (the visit's role and style per pitch), or the tour is refused.

FieldTypeRequiredMeaning
gradesgradesnoThe pitch's grade ({ "french": "6b" }), in a rock, ice or mixed scale.
lengthMnumber from 0 to 500noThe pitch's length in metres.
rolerolenoThis pitch's role, when it differs from the route's.
stylestylenoThis pitch's style, when it differs from the route's.

A climbing route's grade is its hardest pitch's, and its length the sum of the pitch lengths. A single-pitch route needs no pitches: its grades are pitch 1.

Topo

One sector's topo: where it is and its routes — no tour, no visit. A rope sector lists routes, a boulder sector its boulders (blocks) with their problems; the other list is refused.

FieldTypeRequiredMeaning
areatopo areain the data importThe climbing area the sector lies in. On a sector's or area's screen it may be left out; given, it must be that area.
sectortopo sectorexcept on a sector's screenThe sector. On a sector's screen it may be left out; given, it must be that sector.
routesarray of topo routesrope sectorThe routes, at least one.
bouldersarray of topo blocksboulder sectorThe blocks, at least one.

Topo area

FieldTypeRequiredMeaning
namestringyesThe area's name ("Balmfluh"). Found by name in its region (see Finding known places), else created.
latnumbernoLatitude in degrees, with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.
regionregionnoCountry and state the area lies in, and a group of your own that holds it. Without a region the area is found only when its name is unique in the logbook.

Topo sector

FieldTypeRequiredMeaning
namestringyesThe sector's name ("Hauptwand"). Found by name inside its area, else created.
discipline"rope" or "boulder"noA rope sector (the default) or a boulder sector.
latnumbernoLatitude in degrees, with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.

Topo block

FieldTypeRequiredMeaning
namestringyesThe block's name ("Ei"). Found by name inside its sector, else created.
latnumbernoLatitude in degrees, with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.
routesarray of topo routesyesThe problems on the block, at least one.

Topo route

A route as the topo describes it. Which fields it may carry depends on its sport, as on a tour's route.

FieldTypeRequiredMeaning
topoIndexwhole number from 0 to 9,999noThe route's number on the topo or the wall (some topos start at 0). The sector lists its routes by it, then the unnumbered ones by name.
namestringyesThe route's name ("Bazillus").
sportsport keynoclimbing on a rope sector and bouldering on a block when left out; ice_climbing and mixed_climbing also lie on a rope sector.
gradesgradesnoThe route's grade ({ "french": "7b" }).
lengthMnumber from 0 to 3,000noThe route's length in metres ("15m" → 15).
pitchesarray of topo pitchesnoA multi-pitch route's pitches in order; left out, the route is one pitch. Pitches without a grade leave the route's grades as its grade until a pitch is graded.
character"plaisir", "partly_bolted", "trad" or "alpine"noHow it is protected, on sport climbing: well bolted, partly bolted, own gear, alpine. Left out, a new route takes plaisir.
notestringnoThe topo's text: description, first ascent, "extension of …".

Topo pitch

FieldTypeRequiredMeaning
gradesgradesnoThe pitch's grade ({ "french": "6b" }).
lengthMnumber from 0 to 500noThe pitch's length in metres.

Location

A destination without a tour (see Capturing destinations).

FieldTypeRequiredMeaning
kind"peak", "place", "area" or "sector"yesWhat it is: a summit, a hut, pass or village, a climbing area, a crag sector.
namestringyesIts name ("Weissmies").
areastringnoSectors only: the area the sector lies in ("Balmfluh"). Left out, the sector stands alone.
discipline"rope" or "boulder"noSectors only: a rope sector (the default) or a boulder sector.
latnumbernoLatitude in degrees, with lon.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.
regionregionnoCountry and state it lies in, with their ISO codes, and a group of your own that holds it.

Challenge list

A list of your own to tick off (see Capturing challenge lists).

FieldTypeRequiredMeaning
keystring of a–z, 0–9 and -yesThe list's identifier ("glarner-gipfel"). A list with the key of one of your lists replaces it; the key of a list that comes with the app is refused.
namestringyesIts name as the challenges tab shows it ("Glarner Gipfel").
targetKind"peak", "hut" or "pass"yesWhat its entries are: summits, huts or passes.
sourcestringnoWhere the list comes from (a book, a website).
versionstringnoThe list's own version ("2026").
targetsarray of challenge targetsyesThe entries, at least one, in the order the tab lists them.

Challenge target

One entry of a challenge list. A name and region code appear at most once per list — unless every entry of that name and code has lat and lon and they lie more than 1000 m apart: then they are namesakes, like the three "Schijen" of Uri, and each is ticked by a location near it.

FieldTypeRequiredMeaning
namestringyesThe summit's, hut's or pass's name ("Tödi"). A tour reaching a location of this name ticks it — a peak on a list of peaks, a place on a list of huts or passes.
latnumbernoLatitude in degrees, with lon — places the entry on the challenge's map, and tells namesakes apart: where several entries or several of your locations share a name, a placed location ticks only the nearest entry within 1000 m.
lonnumbernoLongitude in degrees, with lat.
elevationMnumber from −500 to 9,000noHeight in metres.
regionNamestringnoShown beside the entry ("Glarus").
regionCodeISO 3166 codenoThe country ("CH") or state ("CH-GL") the entry stands for, in a list of one per region.
aliasesarray of stringsnoOther names a tour may give the place (["Piz Russein"]).

Grades

A grades object maps a scale to a grade token of that scale: { "french": "6a+" }, { "alpine": "ZS-", "uiaa": "IV" }. Scales of one family (French, UIAA, YDS and Norwegian are all rock; Font and V are boulder) are one grade — give one of them, the one the spreadsheet uses. A token must be written exactly as in the table below (Font and V also accept lower case); anything else is refused. A sport accepts the scales listed for it in Sports.

Convert nothing yourself: a grade written in UIAA stays UIAA in the file, and the app shows it in whichever scale the user reads.

Grade scales

ScaleFamilyTokens
frenchrock1 2 3a 3b 3c 4a 4b 4c 5a 5a+ 5b 5b+ 5c 5c+ 6a 6a+ 6b 6b+ 6c 6c+ 7a 7a+ 7b 7b+ 7c 7c+ 8a 8a+ 8b 8b+ 8c 8c+ 9a 9a+ 9b 9b+ 9c
uiaarockI II II+ III- III III+ IV- IV IV+ V- V V+ VI- VI VI+ VII- VII VII+ VIII- VIII VIII+ IX- IX IX+ X- X X+ XI- XI XI+ XII- XII
ydsrock5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 5.10a 5.10b 5.10c 5.10d 5.11a 5.11b 5.11c 5.11d 5.12a 5.12b 5.12c 5.12d 5.13a 5.13b 5.13c 5.13d 5.14a 5.14b 5.14c 5.14d 5.15a 5.15b 5.15c 5.15d
norwegianrock1- 1 1+ 2- 2 2+ 3- 3 3+ 4- 4 4+ 5- 5 5+ 6- 6 6+ 7- 7 7+ 8- 8 8+ 9- 9 9+ 10- 10 10+ 11- 11 11+
fontboulder3 4 4+ 5 5+ 6A 6A+ 6B 6B+ 6C 6C+ 7A 7A+ 7B 7B+ 7C 7C+ 8A 8A+ 8B 8B+ 8C 8C+ 9A
vscaleboulderVB V0 V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17
iceiceWI1 WI2 WI3 WI3+ WI4 WI4+ WI5 WI5+ WI6 WI6+ WI7 WI7+
alpinealpineL WS- WS WS+ ZS- ZS ZS+ S- S S+ SS- SS SS+ AS EX
mixedmixedM1 M2 M3 M4 M5 M6 M7 M8 M9 M10 M11 M12
hikinghikingT1 T2 T3 T4 T4+ T5 T5+ T6
mtbmtbS0 S1 S2 S3 S4 S5
via_ferratavia_ferrataK1 K2 K3 K4 K5 K6
snowshoesnowshoeWT1 WT2 WT3 WT4 WT5 WT6

The SAC alpine scale is written with its German letters: L easy, WS moderately difficult, ZS fairly difficult, S difficult, SS very difficult, AS extremely difficult, EX excessively difficult (French F, PD, AD, D, TD, ED, ABO map to them in that order).

Sports

What each sport records. Metrics are the segment fields it takes, destinations whether its segments list peaks and places, routes on the location kinds its routes hang off, pitches whether its routes have them (required: every route is at least one pitch), visit what a route visit answers, scales the grade scales it accepts, segment grade whether the segment itself takes a grade.

KeyNameMetricsDestinationsRoutes onPitchesVisitScalesSegment grade
hikingHikingdistanceKm gainM lossM durationpeaks placespeak, place—outcomehikingyes
scramblingScramblingdistanceKm gainM lossM durationpeaks placespeak, place—outcomehiking french uiaa yds norwegian—
alpineMountaineeringdistanceKm gainM lossM durationpeaks placespeak, placeoptionaloutcomealpine french uiaa yds norwegian—
climbingClimbingdistanceKm gainM lossM durationpeaks placessector, peakrequiredstyle role attemptsfrench uiaa yds norwegian—
ski_touringSki TouringdistanceKm gainM lossM durationpeaks placespeak, place—outcomealpine—
xc_skiingCross-Country SkiingdistanceKm gainM lossM durationplacesplace————
runningRunningdistanceKm gainM lossM durationplacesplace————
orienteeringOrienteeringdistanceKm gainM lossM durationplacesplace————
mtbMountain BikingdistanceKm gainM lossM durationpeaks placesplace, peak—outcomemtbyes
cyclingCyclingdistanceKm gainM lossM durationplacesplace————
trail_runningTrail RunningdistanceKm gainM lossM durationpeaks placespeak, place—outcomehikingyes
ice_climbingIce ClimbingdistanceKm gainM lossM durationpeaks placessector, peakrequiredstyle role attemptsice—
mixed_climbingMixed ClimbingdistanceKm gainM lossM durationpeaks placessector, peakrequiredstyle role attemptsmixed ice—
paraglidingParaglidingdistanceKm gainM lossM durationpeaks placesplace, peak————
boulderingBoulderingdistanceKm gainM lossM durationplacesboulder—style attemptsfont vscale—
ski_mountaineeringSki MountaineeringdistanceKm gainM lossM durationpeaks placespeak, placeoptionaloutcomealpine french uiaa yds norwegian—
via_ferrataVia FerratadistanceKm gainM lossM durationpeaks placespeak, place—outcomevia_ferrata—
snowshoeingSnowshoeingdistanceKm gainM lossM durationpeaks placespeak, place—outcomesnowshoeyes

Choosing the sport: a glacier tour or a high summit on foot is alpine (mountaineering); a ski tour to a high summit, roped where it gets steep, ski_mountaineering; a plain ski tour ski_touring; sport and multi-pitch rock routes climbing; T-graded walks hiking, T4–T6 terrain with hands on rock scrambling. Indoor climbing has no sport — leave it out or ask. Riding on road, gravel or a cycle path is cycling; on trails, mtb. A file written before 0.1.0 may still say road_cycling: it is read as cycling, but write cycling.

Visit answers

Styles

On the style sports (style in the visit column), the route's and each pitch's style. Bouldering takes flash, redpoint, repeat, attempt and onsight only.

StyleMeaning
onsightFirst go, no prior information, no fall.
flashFirst go with prior information (beta), no fall.
redpointLed free after earlier tries.
repeatClimbed free again, sent before.
french_freePulled on gear or bolts in places (A0).
aidClimbed on gear.
attemptNot topped out — a fall-off, a retreat.

Roles

On the rope sports, the route's role and each pitch's.

RoleMeaning
leadLed every pitch (the default).
followSeconded.
alternatingSwung leads.

Outcomes

On the outcome sports, a route's outcome; on every sport with peaks, a peak's outcome.

OutcomeMeaning
reachedGot to the top or the end (the default).
turned_backTurned back before it.

Values

What the import does

Finding known places

A peak, place, area or loose sector named in the file is matched to what the logbook already holds — **including the peaks and places it took from a region pack**, which most picked ones are — by its name (case and spacing ignored) and kind:

Mapping a spreadsheet

Typical columns and where they go (the column names are examples — German logbooks often use the ones on the left):

ColumnField
Datum, Datedate
Tour, Ziel, Name, Titelname
Sportart, Aktivität, Typsegments[].sport — map each value to a key from Sports (Wandern → hiking, Hochtour → alpine, Skitour → ski_touring, Skihochtour → ski_mountaineering, Klettern → climbing, Bouldern → bouldering, Klettersteig → via_ferrata, Schneeschuh → snowshoeing, Bike/MTB → mtb, Velo/Rennrad/Radfahren → cycling, Laufen/Joggen → running, Trailrunning → trail_running, Langlauf → xc_skiing, Gleitschirm → paragliding, Eisklettern → ice_climbing)
Partner, Begleitung, Mitpartners — split "Anna, Beat & Carla" into three names
Gipfel, Bergsegments[].peaks[].name
Hütte, Ort, Pass, Startsegments[].places[].name
Höhenmeter, Aufstieg, Hm ↑segments[].gainM
Abstieg, Hm ↓segments[].lossM
Distanz, Strecke, kmsegments[].distanceKm
Zeit, Dauer, Gehzeitsegments[].duration as "h:mm"
Schwierigkeit, Grad, Bewertunga route's grades (or the segment's grade on hiking, trail running, MTB, snowshoeing) with the scale the value is written in: T3 → hiking, ZS → alpine, 6a → french, V+ → uiaa, WT2 → snowshoe, K3 → via_ferrata
Route, Weg, Liniesegments[].routes[].name, on its peak or sector
Gebiet, Klettergebieta route's area
Sektor, Wanda route's sector
Stil, Begehunga route's style (OS → onsight, Flash → flash, RP/Rotpunkt → redpoint, AF/A0 → french_free, Versuch → attempt)
Seillängenthe number of pitches (with their grades when listed)
Gipfel erreicht? Abbruch, Umkehra peak's outcome (turned_back for an abandoned summit)
Region, Kanton, Land, Gebietregion (country, state with their ISO codes countryCode, stateCode; a valley or range as group)
Bemerkung, Notizen, Kommentardescription
Saison, Reise, Projektcollection
Stichworte, Labelstags

A row with two sports (hike in, climb, walk out; ski up, fly down) is one tour with several segments. Two rows on the same day for one outing are one tour; two outings on one day are two tours with different names. When a row does not have a name, build one from what it has ("Pizol", "Pizol – Wildsee"), since the name identifies the tour on a re-run.

Example

{
  "format": "tourbook-import",
  "version": 1,
  "tours": [
    {
      "date": "2024-06-02",
      "name": "Pizol – Five lakes",
      "description": "Hot day, the upper lakes still half frozen.",
      "region": { "country": "Schweiz", "countryCode": "CH", "state": "St. Gallen", "stateCode": "CH-SG" },
      "partners": ["Anna", "Beat"],
      "tags": ["Family"],
      "segments": [
        {
          "sport": "hiking",
          "distanceKm": 12.5,
          "gainM": 1100,
          "lossM": 1100,
          "duration": "5:30",
          "grade": { "hiking": "T3" },
          "peaks": [{ "name": "Pizol", "lat": 46.9586, "lon": 9.3897, "elevationM": 2844 }],
          "places": [{ "name": "Wangs" }]
        }
      ]
    },
    {
      "date": "2024-07-20",
      "name": "Weissmies",
      "region": { "country": "Schweiz", "countryCode": "CH", "state": "Wallis", "stateCode": "CH-VS", "group": "Saastal" },
      "collection": "Hochtourenwoche 2024",
      "partners": ["Beat"],
      "segments": [
        {
          "sport": "alpine",
          "gainM": 1350,
          "lossM": 1350,
          "duration": "7:15",
          "places": [{ "name": "Hohsaas" }],
          "routes": [{ "name": "Normal route", "peak": "Weissmies", "grades": { "alpine": "WS" }, "outcome": "reached" }]
        }
      ]
    },
    {
      "date": "2024-07-21",
      "name": "Lagginhorn",
      "region": { "country": "Schweiz", "countryCode": "CH", "state": "Wallis", "stateCode": "CH-VS", "group": "Saastal" },
      "collection": "Hochtourenwoche 2024",
      "segments": [
        {
          "sport": "alpine",
          "peaks": [{ "name": "Lagginhorn", "outcome": "turned_back" }],
          "routes": [{ "name": "West ridge", "peak": "Lagginhorn", "grades": { "alpine": "WS+", "uiaa": "III" } }]
        }
      ]
    },
    {
      "date": "2024-09-14",
      "name": "Wendenstöcke",
      "segments": [
        { "sport": "hiking", "name": "Approach", "gainM": 450, "duration": "1:15" },
        {
          "sport": "climbing",
          "routes": [
            {
              "name": "Motörhead",
              "area": "Gadmen",
              "sector": "Wendenstöcke",
              "style": "redpoint",
              "role": "lead",
              "pitches": [
                { "grades": { "french": "6a" }, "lengthM": 35 },
                { "grades": { "french": "6b+" }, "lengthM": 30, "role": "follow" },
                { "grades": { "french": "6a+" }, "lengthM": 40 }
              ]
            },
            { "name": "Warm-up", "area": "Gadmen", "sector": "Wendenstöcke", "grades": { "uiaa": "V+" }, "style": "onsight" }
          ]
        }
      ]
    },
    {
      "date": "2024-10-05",
      "name": "Chironico",
      "partners": ["Carla"],
      "segments": [
        {
          "sport": "bouldering",
          "routes": [
            { "name": "Dreamtime", "sector": "Chironico Sinistra", "boulder": "Dreamtime block", "grades": { "font": "8B+" }, "style": "attempt", "attempts": 12 },
            { "name": "Ganesh", "sector": "Chironico Sinistra", "boulder": "Ganesh block", "grades": { "font": "7C" }, "style": "flash" }
          ]
        }
      ]
    },
    {
      "date": "2025-02-08",
      "name": "Niesen hike & fly",
      "segments": [
        { "sport": "snowshoeing", "gainM": 1600, "duration": "4:10", "grade": { "snowshoe": "WT2" }, "peaks": [{ "name": "Niesen", "elevationM": 2362 }] },
        { "sport": "paragliding", "name": "Flight down", "lossM": 1650, "duration": "0:25" }
      ]
    }
  ]
}

Capturing a sector topo

A climbing guide, a topo page (theCrag, a club's website), a PDF or a photo of the board at the crag lists a sector's routes: a number, a name, a grade, a length, a line of text. An AI given this page and that listing writes a file of topos — the area, the sector and its routes, with no tour — and the app adds them to the logbook as places and routes you have not climbed yet. Your later tours find them by name.

There are two ways in:

Existing routes are never overwritten. A route that the sector already holds — same sport, same name (case and spacing ignored) — keeps its grades, length, pitches and character exactly as they are, and is reported as already present; only a topo number or note it does not have yet is added. Only new routes are created. Importing the same file again therefore adds nothing. A name spelled differently from the logbook's ("Bazilus" beside "Bazillus") becomes a second route: join the two afterwards with the route merge — on the sector's screen, the route's ⋯ › Merge with another route…, or Settings › Tools › Data quality › Duplicate routes, which finds such pairs. The visits move to the route you keep, and its topo takes what it lacked.

One topo is one transaction: a sector comes in whole or not at all, and the report lists per sector whether area and sector were new, how many routes were created and how many were already there.

Mapping a topo listing:

On the topoField
The number in front of the route (0, 1, 12)topoIndex
The route's namename
The grade (7b, 6a+, VI+, 7A)grades with its scale: French 7b → { "french": "7b" }, UIAA VI+ → { "uiaa": "VI+" }, a boulder grade 7A → { "font": "7A" }
The length (15m, 25 m)lengthM as a number: 15
Pitches (3 SL, a grade per pitch)pitches, one entry per pitch with its grade and length; with only the route's grade (5c, 2 SL), the grade in the route's grades and one empty entry {} per pitch
"Sport", "bolted"nothing — sport climbing on bolts is the default (character plaisir); "trad" → trad, "partly bolted" → partly_bolted
A description, "Extension of …", "Erstbegehung: …", the first ascentnote, as written
Stars, a quality rating, ascent counts, a photonothing — not stored; leave them out

Write each name exactly as the topo spells it, and keep the topo's own grade scale. Leave out what the listing does not say; never fill a grade or a length from elsewhere.

A topo for the data import:

{
  "format": "tourbook-import",
  "version": 1,
  "topos": [
    {
      "area": { "name": "Balmfluh", "region": { "country": "Schweiz", "countryCode": "CH", "state": "Solothurn", "stateCode": "CH-SO" } },
      "sector": { "name": "Hauptwand", "discipline": "rope" },
      "routes": [
        { "topoIndex": 1, "name": "Bazillus", "grades": { "french": "7b" }, "lengthM": 12 },
        { "topoIndex": 2, "name": "3 Tiger & 1 Baby", "grades": { "french": "7c+" }, "lengthM": 15, "note": "Extension of Bazillus." },
        {
          "topoIndex": 3,
          "name": "Pipopa",
          "grades": { "french": "8b" },
          "note": "rechte Verlängerung zu 'Himmel und Hölle'. Erstbegehung: Stefan Schibli"
        },
        {
          "topoIndex": 4,
          "name": "Lange Reise",
          "pitches": [
            { "grades": { "french": "6a" }, "lengthM": 30 },
            { "grades": { "french": "6b+" }, "lengthM": 25 }
          ]
        }
      ]
    },
    {
      "area": { "name": "Balmfluh", "region": { "country": "Schweiz", "countryCode": "CH", "state": "Solothurn", "stateCode": "CH-SO" } },
      "sector": { "name": "Blockfeld", "discipline": "boulder" },
      "boulders": [
        { "name": "Ei", "routes": [{ "topoIndex": 1, "name": "Dotter", "grades": { "font": "6A" } }] }
      ]
    }
  ]
}

The same routes on the screen of sector "Hauptwand" — no area, no sector:

{
  "format": "tourbook-import",
  "version": 1,
  "routes": [
    { "topoIndex": 1, "name": "Bazillus", "grades": { "french": "7b" }, "lengthM": 12 },
    { "topoIndex": 5, "name": "Vitalstoffe", "grades": { "french": "7a" }, "lengthM": 15 }
  ]
}

Capturing destinations

A list of summits, huts or crags — a peak-bagging list, a guide's table of contents, the places of an old logbook — becomes a file of locations: each with its kind, name and what the list says of it. The app adds the ones it does not know and leaves the known ones as they are; your later tours find them by name.

{
  "format": "tourbook-import",
  "version": 1,
  "locations": [
    { "kind": "peak", "name": "Weissmies", "lat": 46.1278, "lon": 8.0118, "elevationM": 4017, "region": { "country": "Schweiz", "countryCode": "CH", "state": "Wallis", "stateCode": "CH-VS", "group": "Saastal" } },
    { "kind": "place", "name": "Almageller Hütte", "elevationM": 2894, "region": { "country": "Schweiz", "countryCode": "CH", "state": "Wallis", "stateCode": "CH-VS" } },
    { "kind": "area", "name": "Balmfluh", "region": { "country": "Schweiz", "countryCode": "CH", "state": "Solothurn", "stateCode": "CH-SO" } },
    { "kind": "sector", "name": "Hauptwand", "area": "Balmfluh", "region": { "country": "Schweiz", "countryCode": "CH", "state": "Solothurn", "stateCode": "CH-SO" } },
    { "kind": "sector", "name": "Blockfeld", "area": "Balmfluh", "discipline": "boulder", "region": { "country": "Schweiz", "countryCode": "CH", "state": "Solothurn", "stateCode": "CH-SO" } }
  ]
}

Capturing challenge lists

A list to tick off — the summits of a canton, the huts of a guidebook, the passes of a road trip — becomes a file of challengeLists: each with its key, name, what its entries are, and the entries. The app shows it on the Statistics › Challenges tab like the lists it comes with: how many are done, which tour reached each, and where they are on the map.

{
  "format": "tourbook-import",
  "version": 1,
  "challengeLists": [
    {
      "key": "glarner-gipfel",
      "name": "Glarner Gipfel",
      "targetKind": "peak",
      "source": "My own list",
      "targets": [
        { "name": "Tödi", "lat": 46.8115, "lon": 8.9146, "elevationM": 3614, "regionName": "Glarus", "regionCode": "CH-GL", "aliases": ["Piz Russein"] },
        { "name": "Glärnisch", "elevationM": 2915, "regionName": "Glarus", "regionCode": "CH-GL" },
        { "name": "Hausstock", "elevationM": 3158 }
      ]
    }
  ]
}

JSON Schema

The shape of the file as a JSON Schema (draft 2020-12). It checks the structure and the vocabularies; which fields each sport records, and which tokens a scale has, the app checks as described above.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "TourBook import file, version 1",
  "description": "The shape of a *.tourbook.json file. Which fields a sport records, and which grade tokens a scale accepts, is checked by the app and described in the guide.",
  "type": "object",
  "properties": {
    "format": { "const": "tourbook-import" },
    "version": { "const": 1 },
    "tours": { "type": ["array", "null"], "items": { "$ref": "#/$defs/tour" } },
    "topos": { "type": ["array", "null"], "items": { "$ref": "#/$defs/topo" } },
    "routes": { "type": ["array", "null"], "items": { "$ref": "#/$defs/topoRoute" } },
    "locations": { "type": ["array", "null"], "items": { "$ref": "#/$defs/location" } },
    "challengeLists": { "type": ["array", "null"], "items": { "$ref": "#/$defs/challengeList" } },
    "$schema": { "type": "string" }
  },
  "required": ["format", "version"],
  "anyOf": [
    { "required": ["tours"], "properties": { "tours": { "type": "array" } } },
    { "required": ["topos"], "properties": { "topos": { "type": "array" } } },
    { "required": ["routes"], "properties": { "routes": { "type": "array" } } },
    { "required": ["locations"], "properties": { "locations": { "type": "array" } } },
    { "required": ["challengeLists"], "properties": { "challengeLists": { "type": "array" } } }
  ],
  "additionalProperties": false,
  "$defs": {
    "text": { "type": "string", "pattern": "\\S" },
    "optionalText": { "type": ["string", "null"] },
    "elevation": { "type": ["number", "null"], "minimum": -500, "maximum": 9000 },
    "pitchLength": { "type": ["number", "null"], "minimum": 0, "maximum": 500 },
    "names": { "type": ["array", "null"], "items": { "$ref": "#/$defs/text" } },
    "grades": {
      "description": "A grade per scale: { \"french\": \"6a\" }. One scale per family (french, uiaa, yds and norwegian are one family).",
      "type": ["object", "null"],
      "propertyNames": {
        "enum": ["french", "uiaa", "yds", "norwegian", "font", "vscale", "ice", "alpine", "mixed", "hiking", "mtb", "via_ferrata", "snowshoe"]
      },
      "additionalProperties": { "type": "string" }
    },
    "tour": {
      "type": "object",
      "properties": {
        "date": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$" },
        "name": { "$ref": "#/$defs/text" },
        "description": { "$ref": "#/$defs/optionalText" },
        "region": { "$ref": "#/$defs/region" },
        "collection": { "$ref": "#/$defs/optionalText" },
        "partners": { "$ref": "#/$defs/names" },
        "tags": { "$ref": "#/$defs/names" },
        "segments": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/segment" } }
      },
      "required": ["date", "name", "segments"],
      "additionalProperties": false
    },
    "region": {
      "type": ["object", "null"],
      "properties": {
        "country": { "$ref": "#/$defs/optionalText" },
        "countryCode": { "type": ["string", "null"], "pattern": "^[A-Z]{2}$" },
        "state": { "$ref": "#/$defs/optionalText" },
        "stateCode": { "type": ["string", "null"], "pattern": "^[A-Z]{2}-[A-Z0-9]{1,3}$" },
        "group": { "$ref": "#/$defs/optionalText" }
      },
      "minProperties": 1,
      "additionalProperties": false
    },
    "segment": {
      "type": "object",
      "properties": {
        "sport": {
          "enum": [
            "hiking",
            "scrambling",
            "alpine",
            "climbing",
            "ski_touring",
            "xc_skiing",
            "running",
            "orienteering",
            "mtb",
            "cycling",
            "trail_running",
            "ice_climbing",
            "mixed_climbing",
            "paragliding",
            "bouldering",
            "ski_mountaineering",
            "via_ferrata",
            "snowshoeing"
          ]
        },
        "name": { "$ref": "#/$defs/optionalText" },
        "distanceKm": { "type": ["number", "null"], "minimum": 0, "maximum": 1000 },
        "gainM": { "type": ["number", "null"], "minimum": 0, "maximum": 15000 },
        "lossM": { "type": ["number", "null"], "minimum": 0, "maximum": 15000 },
        "duration": { "type": ["string", "null"], "pattern": "^\\d+:[0-5]\\d(:[0-5]\\d)?$" },
        "grade": { "$ref": "#/$defs/grades" },
        "peaks": { "type": ["array", "null"], "items": { "$ref": "#/$defs/peak" } },
        "places": { "type": ["array", "null"], "items": { "$ref": "#/$defs/place" } },
        "routes": { "type": ["array", "null"], "items": { "$ref": "#/$defs/route" } }
      },
      "required": ["sport"],
      "additionalProperties": false
    },
    "peak": {
      "type": "object",
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" },
        "outcome": { "enum": ["reached", "turned_back", null] }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "place": {
      "type": "object",
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "route": {
      "type": "object",
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "peak": { "$ref": "#/$defs/optionalText" },
        "place": { "$ref": "#/$defs/optionalText" },
        "area": { "$ref": "#/$defs/optionalText" },
        "sector": { "$ref": "#/$defs/optionalText" },
        "boulder": { "$ref": "#/$defs/optionalText" },
        "grades": { "$ref": "#/$defs/grades" },
        "pitches": { "type": ["array", "null"], "items": { "$ref": "#/$defs/pitch" } },
        "style": { "enum": ["onsight", "flash", "redpoint", "repeat", "french_free", "aid", "attempt", null] },
        "role": { "enum": ["lead", "follow", "alternating", null] },
        "attempts": { "type": ["integer", "null"], "minimum": 1, "maximum": 999 },
        "outcome": { "enum": ["reached", "turned_back", null] }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "pitch": {
      "type": "object",
      "properties": {
        "grades": { "$ref": "#/$defs/grades" },
        "lengthM": { "$ref": "#/$defs/pitchLength" },
        "role": { "enum": ["lead", "follow", "alternating", null] },
        "style": { "enum": ["onsight", "flash", "redpoint", "repeat", "french_free", "aid", "attempt", null] }
      },
      "additionalProperties": false
    },
    "topo": {
      "type": "object",
      "properties": {
        "area": { "$ref": "#/$defs/topoArea" },
        "sector": { "$ref": "#/$defs/topoSector" },
        "routes": { "type": ["array", "null"], "items": { "$ref": "#/$defs/topoRoute" } },
        "boulders": { "type": ["array", "null"], "items": { "$ref": "#/$defs/topoBoulder" } }
      },
      "additionalProperties": false
    },
    "topoArea": {
      "type": ["object", "null"],
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" },
        "region": { "$ref": "#/$defs/region" }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "topoSector": {
      "type": ["object", "null"],
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "discipline": { "enum": ["rope", "boulder", null] },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "topoBoulder": {
      "type": "object",
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" },
        "routes": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/topoRoute" } }
      },
      "required": ["name", "routes"],
      "additionalProperties": false
    },
    "topoRoute": {
      "type": "object",
      "properties": {
        "topoIndex": { "type": ["integer", "null"], "minimum": 0, "maximum": 9999 },
        "name": { "$ref": "#/$defs/text" },
        "sport": { "enum": ["climbing", "ice_climbing", "mixed_climbing", "bouldering", null] },
        "grades": { "$ref": "#/$defs/grades" },
        "lengthM": { "type": ["number", "null"], "minimum": 0, "maximum": 3000 },
        "pitches": { "type": ["array", "null"], "items": { "$ref": "#/$defs/topoPitch" } },
        "character": { "enum": ["plaisir", "partly_bolted", "trad", "alpine", null] },
        "note": { "$ref": "#/$defs/optionalText" }
      },
      "required": ["name"],
      "additionalProperties": false
    },
    "topoPitch": {
      "type": "object",
      "properties": {
        "grades": { "$ref": "#/$defs/grades" },
        "lengthM": { "$ref": "#/$defs/pitchLength" }
      },
      "additionalProperties": false
    },
    "location": {
      "description": "A destination without a tour. `area` and `discipline` are given on a sector only.",
      "type": "object",
      "properties": {
        "kind": { "enum": ["peak", "place", "area", "sector"] },
        "name": { "$ref": "#/$defs/text" },
        "area": { "$ref": "#/$defs/optionalText" },
        "discipline": { "enum": ["rope", "boulder", null] },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" },
        "region": { "$ref": "#/$defs/region" }
      },
      "required": ["kind", "name"],
      "additionalProperties": false
    },
    "challengeList": {
      "description": "A challenge list of your own: summits, huts or passes to tick off. A list with the key of one of your lists replaces it.",
      "type": "object",
      "properties": {
        "key": { "type": "string", "pattern": "^[a-z0-9-]+$" },
        "name": { "$ref": "#/$defs/text" },
        "targetKind": { "enum": ["peak", "hut", "pass"] },
        "source": { "$ref": "#/$defs/optionalText" },
        "version": { "$ref": "#/$defs/optionalText" },
        "targets": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/challengeTarget" } }
      },
      "required": ["key", "name", "targetKind", "targets"],
      "additionalProperties": false
    },
    "challengeTarget": {
      "description": "One entry of a challenge list. A name and region code appear once per list, unless every entry of that name and code has lat and lon and they lie more than 1000 m apart.",
      "type": "object",
      "properties": {
        "name": { "$ref": "#/$defs/text" },
        "lat": { "type": ["number", "null"], "minimum": -90, "maximum": 90 },
        "lon": { "type": ["number", "null"], "minimum": -180, "maximum": 180 },
        "elevationM": { "$ref": "#/$defs/elevation" },
        "regionName": { "$ref": "#/$defs/optionalText" },
        "regionCode": { "type": ["string", "null"], "pattern": "^[A-Z]{2}(-[A-Z0-9]{1,3})?$" },
        "aliases": { "$ref": "#/$defs/names" }
      },
      "required": ["name"],
      "additionalProperties": false
    }
  }
}