globalhelplines.org

API reference

A read-only JSON API over the verified crisis-helpline dataset. Records are returned verbatim — the API never rewrites a number, a URL, or an opening hour.

Base URL
https://globalhelplines.org
Format
JSON. GET only on /v1/*. CORS open for GET and OPTIONS.
Page size
Fixed at 10 records. Use page to walk results.
Ordering
Best-verified first — existence grade ascending (A+ → A → B → …, ungraded last), tiebroken by id.

Authentication

Optional. Without a key you're served on the anonymous tier, identified by IP. With a key, send it either way:

Authorization: Bearer hl_<40 hex>
x-api-key: hl_<40 hex>

A key that is unknown or revoked returns 401 invalid_api_key. There is no silent fallback to anonymous — a presented credential that doesn't work is always an error. Keys are issued by the operator; see access tiers.

Rate limits & tiers

Two axes are metered: requests, and records returned. Windows are UTC — daily for anonymous, monthly for keyed tiers. Invalid requests (400) consume no quota.

Tier
Identity
Requests
Records
Price
Anonymous
ip:<address>
~10 / day
50 / day
Free
Dev
key:<key_id>
~500 / month
2,500 / month
Free
Nonprofit
key:<key_id>
~50,000 / month
250,000 / month
Free — after verification
Commercial
key:<key_id>
Negotiated
Negotiated
Contact us
Contact us about commercial access Commercial usage is metered and billed — limits are agreed per account.

Two things are counted

Requests, and the number of records those requests returned. Whichever cap you reach first is the one that stops you. Counting records as well as calls is what makes bulk extraction impractical without making ordinary lookups feel tight — a typical country-and-topic query returns well under a full page.

Windows reset on UTC boundaries

The anonymous tier resets at UTC midnight; keyed tiers reset at the start of the UTC month. Nothing rolls — a window either has budget left or it doesn't, and a 429 tells you exactly how many seconds until the next one opens.

A burst guard sits on top

Independent of your tier, a short-window guard caps any single caller at 30 requests per minute. It exists to absorb runaway loops and scrapers, not to constrain real traffic — you will only meet it if something is misbehaving. Higher burst limits available for commercial customers.

You can always see where you stand

Every /v1/* response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Window, so you never have to guess. A 429 adds a standard Retry-After header alongside retry_after_seconds in the body.

Bad requests are free

A 400 costs nothing — malformed parameters are rejected before quota is touched, so you can develop against the API without burning a day's budget on typos. Discovery lookups are free too.

If you're about to run out

Cache what you fetch — helpline records change slowly and carry a lastVerified date you can revalidate against. If you're a nonprofit or crisis service, ask for the nonprofit tier rather than rationing the free one.

Errors

One shape, every failure:

{ "error": { "code": "invalid_parameter", "message": "…", "parameter": "topic",
             "allowed": ["suicide", "domestic_violence", …] } }
HTTP
code
When
400
invalid_parameter
Missing or malformed param. Enum params return the full allowed list.
401
invalid_api_key
Key unknown or revoked.
404
not_found
Well-formed service id with no record.
429
rate_limited
Extras: tier, limit, limit_kind (requests / records / burst), window, retry_after_seconds.
500
internal_error
Generic message only; details go to logs, never the response.

A well-formed but unknown country code is not an error — it returns 200 with total: 0 and an empty results array. Same for a page past the end.

Service record schema

Nullable fields really are null in the data rather than absent, and several are deliberately loose because the underlying reality is. Where a field can be a string or an array of strings, handle both.

Returns — one service record, complete
{
  "id": "US-015",
  "name": "988 Suicide & Crisis Lifeline",
  "name_original": "988 Suicide & Crisis Lifeline",
  "categories": [
    "suicide",
    "mental_health",
    "substance_abuse",
    "general_crisis"
  ],
  "description": "The national 988 Suicide & Crisis Lifeline provides free, confidential support 24/7 via call, text, or chat to anyone experiencing a mental health, suicidal, or substance use crisis. Local calls are routed to the nearest of 180+ regional crisis centers.",
  "target_audience": "general",
  "contact": {
    "phone": "988",
    "sms": "988",
    "whatsapp": null,
    "chat_url": "https://chat.988lifeline.org/",
    "email": null,
    "app": null,
    "in_person": null
  },
  "hours": "24/7",
  "hours_structured": "24/7",
  "languages": ["en", "es"],
  "coverage": "national",
  "cost": "free",
  "anonymous": true,
  "website": "https://988lifeline.org/",
  "source_url": "https://988lifeline.org/",
  "source_type": "official_site",
  "notes": "Spanish speakers and ASL users served. Veterans press 1. The dedicated LGBTQ+ youth 'press 3' sub-service faced a federal defunding effort in mid-2025; verify current status of that specific option separately if serving LGBTQ+ youth callers.",
  "grades": {
    "existence": "A+",
    "accuracy": "A",
    "quality": "A"
  },
  "grade_rationale": "Official national site 988lifeline.org loaded and confirmed: name, phone 988, SMS 988, 24/7/365 hours, English and Spanish. Independently confirmed via Alabama AMHH government page (name, phone, Spanish chat, veterans press-1, regional call centers). Chat URL corrected in a prior pass from 988lifeline.org/chat/ to chat.988lifeline.org/ (verified) and retained here. Quality A: SAMHSA-administered federal programme; all network centres require accreditation; multi-year operating history (July 2022 launch, predecessor since 2005).",
  "validated_at": "2026-08-20",
  "validation_sources": [
    "https://988lifeline.org/",
    "https://mh.alabama.gov/988-2/",
    "https://www.samhsa.gov/mental-health/988"
  ],
  "corrections": {
    "contact.chat_url": {
      "raw": "https://988lifeline.org/chat/",
      "corrected": "https://chat.988lifeline.org/"
    }
  },
  "added": "2026-05-29",
  "lastVerified": "2026-08-20",
  "lastUpdated": "2026-08-20"
}

Identity

Field
Type
Meaning
id
string
{ISO2}-{NNN} for country-wide services, or {ISO2}-{SUBDIVISION}-{NNN} where the service is region-specific (e.g. US-CA-002). Unique across the whole dataset.
name
string
Name in English, transliterated where no English name exists.
name_original
string | null
Name in the local language or script. Same as name when the service is originally English-named.
description
string | null
One to three sentences on what the service does.
notes
string | null
Edge cases worth knowing: regional variations, partial coverage, known reachability problems, sub-services with a separate status.

Classification

Field
Type
Meaning
categories
string[]
One or more topic values. A service commonly sits in several — a suicide line is often also mental_health and general_crisis.
target_audience
string
general | women | men | youth | children | lgbtq | elderly | veterans | migrants | refugees, or free text where none fits.
coverage
string
national | regional:{name} | city:{name} | international. Human-readable, and additive to region.
region
string | null
Full ISO 3166-2 subdivision code (US-CA, BO-L, CD-NK) when the service is specific to one sub-national area. Null or absent when it applies country-wide.
cost
enum
free | free_from_landlines | local_rate | standard_rate | paid | unknown. unknown is a real value — cost is recorded, not assumed.
anonymous
boolean | "unknown"
Whether the service can be used without identifying yourself. Three-valued, not two.
languages
string[]
ISO 639-1 codes the service actually answers in — not the languages spoken in the country.

Contact & availability

Field
Type
Meaning
contact.phone
string | string[] | null
International format where possible, with the commonly-cited national format alongside. May be several numbers.
contact.sms
string | string[] | null
SMS shortcode or number.
contact.whatsapp
string | string[] | null
WhatsApp-specific number.
contact.chat_url
string | string[] | null
Web-chat URL.
contact.email
string | string[] | null
Contact address, where the service accepts email.
contact.app
string | string[] | null
App name plus store URL.
contact.in_person
string | string[] | null
Brief description of walk-in availability, sometimes an address.
hours
string | null
Free text, in the local convention where natural. Present it as given.
hours_structured
object | string | null
Either a weekday-to-range map, or the literal string "24/7", or null. The shape genuinely varies — branch on it rather than assuming.

Provenance & validation

Field
Type
Meaning
website
string | null
The service's own primary URL. Null where it has none.
source_url
string
Where the information was found. Always present.
source_type
enum
official_site | government_directory | ngo_directory | wikipedia | news_article | academic | social_media | forum | other.
grades.existence
grade | null
Confidence the service is operating and reachable as described. A+ down to F, or unknown.
grades.accuracy
grade | null
Confidence the contact details, hours and languages are current and correct.
grades.quality
grade | absent
How well-regarded the service is — reviews, reputation, accreditation. Absent where no reputational audit was performed.
grade_rationale
string
One to three sentences stating what was actually checked, and why the grades landed where they did.
validation_sources
string[]
Every URL consulted during the validating pass, not just the primary one.
corrections
object
Fields the validation pass changed, as {field: {raw, corrected}}. On canonicalized US national records it is free-text prose instead, documenting the collapse.
added
date
ISO date the entry first appeared. Set once and preserved.
lastVerified
date
ISO date of the most recent pass that successfully fetched a source. Updated every run, whether or not anything changed.
lastUpdated
date | null
ISO date of the most recent run that actually changed data. Null when no correction has ever applied.
validated_at
date
Back-compatible alias of lastVerified.
GET /v1/helplines

Find helplines for one country and one topic. Both are required — the pairing is what keeps the endpoint a lookup rather than a bulk export.

Query parameters

Name
Required
Rules
country
yes
ISO 3166-1 alpha-2. Trimmed and uppercased; must match two letters. A well-formed but unknown code returns 200 with total: 0.
topic
yes
One of the category values from /v1/topics. Trimmed and lowercased; anything else returns 400 listing every allowed value.
region
no
Sub-national code (US states + DC, AU jurisdictions). Returns the region's lines plus the national ones.
language
no
ISO 639-1 code the service actually answers in. Validated by shape, not against a fixed enum — /v1/languages is the source of truth.
contact_method
no
phone | sms | whatsapp | chat | email | app | in_person. Anything else returns 400 with the allowed list.
cost
no
Cost band. See /v1/costs for the current enum.
anonymous
no
Filter to services that can be used anonymously.
keyword
no
2–100 characters after trimming. Case-insensitive substring match within the country + topic slice.
page
no
Integer 1–1000, default 1. Pages past the end return 200, empty results, and the true total.
Request
curl "https://globalhelplines.org/v1/helplines?country=US&topic=suicide®ion=CA" \
  -H "Authorization: Bearer $HELPLINES_KEY"
Returns — 200 · application/json
{
  "meta": { "dataset_version": "…", "disclaimer": "…" },
  "query": { "country": "US", "topic": "suicide",
             "region": "CA", "page": 1 },
  "page": 1,
  "page_size": 10,
  "total": 14,
  "results": [ /* service records */ ]
}

A region query returns the region's own lines plus the national ones. This is deliberate: a state filter must never hide a national crisis line.

GET /v1/services/{id}

Fetch one service by its exact id, with full validation provenance. Use it to re-verify a record you already hold — it is not a search endpoint.

Name
Required
Rules
id
yes
Path segment. Trimmed and uppercased; must match ^[A-Z]{2}-\d{3}$ (e.g. US-015). Malformed → 400, unknown → 404.
Request · Returns — 200
curl "https://globalhelplines.org/v1/services/US-015"

{ "meta": { … }, "service": { /* full record */ } }

Discovery endpoints

Reference lists for populating filters and validating input. These are the authoritative enumerations — prefer them over hardcoding. No key required, and they don't consume your query quota.

GET /v1/topics

The authoritative topic enum — the only valid values for the topic parameter. A constant, so it never varies with the data.

Returns — 200 · application/json
{
  "topics": [
    "suicide", "domestic_violence", "domestic_violence_women",
    "domestic_violence_men", "sexual_assault", "child_abuse",
    "child_helpline", "lgbtq", "substance_abuse", "mental_health",
    "eating_disorder", "bereavement", "elder_abuse",
    "human_trafficking", "victim_support", "gambling",
    "general_crisis", "homelessness", "refugee_migrant",
    "veteran_support", "veteran_crisis", "veterans", "youth"
  ]
}
GET /v1/contact-methods

Valid contact_method values. Also a constant.

Returns — 200 · application/json
{
  "contact_methods": ["phone", "sms", "whatsapp", "chat",
                      "email", "app", "in_person"]
}
GET /v1/costs

Valid cost values. Note that unknown is a real member — cost is recorded, not assumed.

Returns — 200 · application/json
{
  "costs": ["free", "free_from_landlines", "local_rate",
            "standard_rate", "paid", "unknown"]
}
GET /v1/countries

ISO 3166-1 alpha-2 codes actually present in the data, sorted. Data-driven — it grows as coverage does.

Returns — 200 · application/json
{
  "countries": ["AE", "AF", "AL", "AM", "AR", "AT", "AU", "BA",
                "BD", "BE", "BG", "BR", /* … */ "US", "VN", "ZA"]
}
GET /v1/regions?country=US

Sub-national codes for one country, as bare parts — exactly what the region parameter accepts, so a value can be fed straight back without slicing.

Returns — 200 · application/json
{
  "regions": ["AK", "AL", "AR", "AZ", "CA", "CO", /* … */ "WY"]
}

// country=AU
{ "regions": ["ACT", "NSW", "NT", "QLD", "SA", "TAS", "VIC", "WA"] }
GET /v1/languages

ISO 639-1 codes services actually answer in — not the languages spoken in the country. Sorted, data-driven.

Returns — 200 · application/json
{
  "languages": ["aa", "ak", "am", "ar", "de", "en", "es", "fr",
                "ko", "pt", /* … */ "zh"]
}
GET /healthz

Liveness. Unauthenticated, unmetered, returns {"ok":true}.