v1 to v2 migration
10 min
in 2026, we began capturing higher fidelity data from our helpline partners among other things, this data gives a much better idea about the opening hours a helpline might serve for each different contact method (rather than assuming one set of hours for all contact methods, which is often inaccurate for many helplines) as this new data requires a fundamentally different data structure it introduces a breaking change to the api, and therefore we launched a new version (v2) and will maintain the old version (v1) for 6 months until november 11th, 2026 whatâs changed contact methods and opening hours these now all belong in one list and are paired together we have also greatly improved the data types, which weâll go into more detail about below before { // "phonenumber" "01 234 567", "webchaturl" "https //www example com/chat", "hours" { 	"alwaysopen" false, 	"periods" \[ 	 { 	 "day" 1, 	 "open" "00 00", 	 "close" "05 00" 	 }, 	 { 	 "day" 1, 	 "open" "17 00", 	 "close" "00 00" 	 }, 	 { 	 "day" 2, 	 "open" "00 00", 	 "close" "05 00" 	 }, 	 { 	 "day" 2, 	 "open" "17 00", 	 "close" "00 00" 	 }, 	 // 	], // } after { // "contactmethods" \[ { "contactmethodtype" "phone", "value" "01 234 5678", "alwaysopen" false, "openinghours" \[ { "localdayofweek" "monday", "open" "2026 04 20t17 00 00+02 00", "close" "2026 04 21t05 00 00+02 00" }, { "localdayofweek" "tuesday", "open" "2026 04 21t17 00 00+02 00", "close" "2026 04 22t05 00 00+02 00" }, // ], // }, { "contactmethodtype" "onlinechat", "value" "https //www example com/chat", "alwaysopen" false, "openinghours" \[ { "localdayofweek" "monday", "open" "2026 04 20t12 00 00+02 00", "close" "2026 04 21t07 00 00+02 00" }, { "localdayofweek" "tuesday", "open" "2026 04 21t12 00 00+02 00", "close" "2026 04 22t07 00 00+02 00" }, // ], // }, ], // } note that in the case that a helpline is open past midnight, we have collapsed those adjacent opening hours to give a more semantically correct representation of the time spans a contact method is open for we also now include a fully qualified iso8601 time representing upcoming opening hours for the next week this way you can easily enumerate the opening timespans using a datetime library comparing to your languageâs now to determine if a contact method is currently open or not; you can also very easily transform those times into your local time using this iso8601 representation const now = new date(); const isopennow = !!contactmethod openinghours some( s => new date(s open) <= now && now < new date(s close) ); const nextopen = contactmethod openinghours\[4]; // for example const nextopenlocally = new date(isostring) tolocaletimestring();require "time" now = time now is open now = contact method\[ openinghours] any? do |s| time iso8601(s\[ open]) <= now && now < time iso8601(s\[ close]) end next open = contact method\[ openinghours]\[4] # for example next open locally = time iso8601(isostring) getlocal strftime("%x")from datetime import datetime now = datetime now() astimezone() is open now = any( datetime fromisoformat(s\["open"]) <= now < datetime fromisoformat(s\["close"]) for s in contact method\["openinghours"] ) next open = contact method\["openinghours"]\[4] # for example next open locally = datetime fromisoformat(isostring) astimezone() strftime("%x")instant now = instant now(); boolean isopennow = contactmethod get("openinghours") stream() anymatch(s > !instant parse(s get("open")) isafter(now) && now\ isbefore(instant parse(s get("close"))) ); var nextopen = contactmethod get("openinghours") get(4); // for example string nextopenlocally = datetimeformatter oflocalizedtime(formatstyle short) withlocale(locale getdefault()) withzone(zoneid systemdefault()) format(instant parse(isostring)); localdayofweek is here as a convenience, especially if you were using the old day field, and in the case of timespans that cross midnight in the helplineâs timezone it will always refer to the day the contact method opens on quick retrieval of contact details one thing this new structure makes slightly harder is for example to get the phone number your user wants, as these used to be accessible top level fields here are some easy ways to do that // before helpline phonenumber // after helpline contactmethods find(cm => cm type === "phone")? value# before helpline\[ phonenumber] \# after helpline\[ contactmethods] find { |cm| cm\[ type] == "phone" }& \[]\(\ value)# before helpline\["phonenumber"] \# after next((cm\["value"] for cm in helpline\["contactmethods"] if cm\["type"] == "phone"), none)// before helpline get("phonenumber"); // after helpline get("contactmethods") stream() filter(cm > cm get("type") equals("phone")) map(cm > cm get("value")) findfirst() orelse(null); the contact method types are phone , sms , online chat , whatsapp , tty , and reporting form ; these are slightly different to the naming conventions used in v1 topics split all topics a helpline supports used to be in one list topics we now record and surface primary and secondary topics, allowing you to better determine which helplines from a list of helplines to show first to achieve the old functionality you can simply concatenate those two lists // before helpline topics // after helpline primarytopics concat(helpline secondarytopics)# before helpline\[ topics] \# after helpline\[ primarytopics] + helpline\[ secondarytopics]# before helpline\["topics"] \# after helpline\["primarytopics"] + helpline\["secondarytopics"]// before helpline get("topics"); // after stream concat(helpline get("primarytopics") stream(), helpline get("secondarytopics") stream()) tolist(); descriptions previously, we had a description and shortdescription field, always in english now, we support descriptions in english as well as the helplineâs local language, where they have provided it therefore we have removed these two fields, and introduced a descriptions list which is a list of languages and the full and short description for each { // "descriptions" \[ { "languagecode" "en", "fulldescription" "we are here to help you, whatever you're going through we provide 24/7, free and confidential support and information by text message, online chat and whatsapp we are available to talk with you about any kind of experience relating to mental health, including anxiety, depression, self harm, stress, and thoughts of suicide ", "shortdescription" "we provide 24/7 support by text message, online chat and whatsapp for any kind of experience relating to mental health " }, { "languagecode" "fr", "fulldescription" "la ligne d'assistance est lĂ pour vous aider, nâimporte que vous traversiez nous offrons un soutien et des informations gratuits, confidentiels et disponibles 24 h/24 et 7 j/7 par sms, clavardage en ligne et whatsapp nous sommes disponibles pour discuter avec vous de tout problĂšme de santĂ© mentale, notamment l'anxiĂ©tĂ©, la dĂ©pression, l'automutilation, le stress et les pensĂ©es suicidaires ", "shortdescription" null } ], // } note that short descriptions are only provided for a select few helplines, and only in english local language descriptions are provided only where helplines volunteer this information similarly to the new contact methods structure, this does create a minimal amount of work to easily get the description youâre after the solution is the same, however // before helpline description // after helpline descriptions find(d => d languagecode === "en")? fulldescription# before helpline\[ description] \# after helpline\[ descriptions] find { |d| d\[ languagecode] == "en" }& \[]\(\ fulldescription)# before helpline\["description"] \# after next((d\["fulldescription"] for d in helpline\["descriptions"] if d\["languagecode"] == "en"), none)// before helpline get("description"); // after helpline get("descriptions") stream() filter(d > d get("languagecode") equals("en")) map(d > d get("fulldescription")) findfirst() orelse(null); or, if you wanted to prioritise the local language description wherever possible (if there is no non english description, the first description if any will be english) helpline descriptions find(d => d languagecode !== "en")? fulldescription || helpline descriptions\[0]? fulldescription (helpline\[ descriptions] find { |d| d\[ languagecode] != "en" } || helpline\[ descriptions] first)& \[]\(\ fulldescription)fallback = helpline\["descriptions"]\[0] if helpline\["descriptions"] else none match = next((d for d in helpline\["descriptions"] if d\["languagecode"] != "en"), fallback) match\["fulldescription"] if match else nonehelpline get("descriptions") stream() filter(d > !d get("languagecode") equals("en")) findfirst() or(() > helpline get("descriptions") stream() findfirst()) map(d > d get("fulldescription")) orelse(null); whatâs new contact methods have accessibility notes helplines can now provide accessibility notes for each contact method ( contactmethods\[] accessibilitynotes ), which you can display to your users alongside the contact information interpretations service we now show if the helpline offers an interpretation service for people who do not speak any of the supported languages ( interpretationserviceprovided ) membership organizations we now list any membership organizations the helpline has informed us theyâre affiliated with ( membershiporganizations )