---
title: v1 to v2 migration
slug: v1-to-v2-migration
docTags: 
createdAt: 2026-04-20T01:52:17.254Z
---

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.

:::hint{type="info"}
Opening hours are available on our Full API tier. [API Tiers](docId\:OCiOfKh07NsqxuXn-VlRY) for more info.
:::



**Before**:

```json
{
  //...
  "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**:

```json
{
  //...
  "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.&#x20;

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.&#x20;

:::CodeblockTabs
```javascript
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();
```

```ruby
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")
```

```python
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")
```

```java
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.

:::CodeblockTabs
```javascript
// Before
helpline.phoneNumber

// After
helpline.contactMethods.find(cm => cm.type === "phone")?.value
```

```ruby
# Before
helpline[:phoneNumber]

# After
helpline[:contactMethods].find { |cm| cm[:type] == "phone" }&.[](:value)
```

```python
# Before
helpline["phoneNumber"]

# After
next((cm["value"] for cm in helpline["contactMethods"] if cm["type"] == "phone"), None)
```

```java
// 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`, `onlineChat`, `whatsapp`, `tty`, and `reportingForm`; 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:

:::CodeblockTabs
```javascript
// Before
helpline.topics

// After
helpline.primaryTopics.concat(helpline.secondaryTopics)
```

```ruby
# Before
helpline[:topics]
  
# After
helpline[:primaryTopics] + helpline[:secondaryTopics]
```

```python
# Before
helpline["topics"]

# After
helpline["primaryTopics"] + helpline["secondaryTopics"]
```

```java
// 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:

```json
{
  //...
  "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.

:::hint{type="info"}
Descriptions are available on our Full API tier. [API Tiers](docId\:OCiOfKh07NsqxuXn-VlRY) for more info.
:::

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:

:::CodeblockTabs
```javascript
// Before
helpline.description

// After
helpline.descriptions.find(d => d.languageCode === "en")?.fullDescription
```

```ruby
# Before
helpline[:description]
  
# After
helpline[:descriptions].find { |d| d[:languageCode] == "en" }&.[](:fullDescription)
```

```python
# Before
helpline["description"]
  
# After
next((d["fullDescription"] for d in helpline["descriptions"] if d["languageCode"] == "en"), None)
```

```java
// 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):

:::CodeblockTabs
```javascript
helpline.descriptions.find(d => d.languageCode !== "en")?.fullDescription
  || helpline.descriptions[0]?.fullDescription
```

```ruby
  (helpline[:descriptions].find { |d| d[:languageCode] != "en" }
    || helpline[:descriptions].first)&.[](:fullDescription)
```

```python
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 None
```

```java
helpline.get("descriptions").stream()
    .filter(d -> !d.get("languageCode").equals("en"))
    .findFirst()
    .or(() -> helpline.get("descriptions").stream().findFirst())
    .map(d -> d.get("fullDescription"))
    .orElse(null);
```
:::

### Case consistency

All endpoints, parameters, property names and values now consistently use consistent casing, across all v2 endpoints. This has the greatest effect in the `/helplines` endpoint and the `/events/*` endpoints.

## 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.&#x20;

### 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`).

