Base URL https://hd2clans.com/api/public
No authentication. CORS is open, so you can call it straight from a browser. Use the batch form if you're watching more than one clan.
import requests
CLANS = [334, 597, 1404, 168, 1381, 1396, 1414, 483,
1325, 395, 179, 1341, 1719, 1388, 902]
r = requests.get(
"https://hd2clans.com/api/public/clan/hangars",
params={"ids": ",".join(map(str, CLANS))},
headers={"User-Agent": "YourAppName/1.0 (+contact: yourdiscord)"},
timeout=30,
)
r.raise_for_status()
for hangar in r.json()["hangars"]:
if not hangar["available"]:
continue
print(hangar["clan"]["name"], hangar["counts"]["total"], "ships")
for ship in hangar["ships"]:
print(" ", ship["name"], ship["hull"]["label"],
ship["condition"]["label"])
Results come back in the order you asked for them. Every entry carries clan_id at the top level whether it resolved or not, so you can match responses to requests without relying on position.
| Field | Type | Notes |
|---|---|---|
| available | bool | False means the clan can't be shown right now. See Unavailable clans. |
| clan_id | int | Always present, on both success and failure. |
| clan | object | id, name, motto, hangar_description, logo_url |
| counts | object | alliance_ships, personal_ships, total, plus personal_deployed / personal_anchored / personal_drydocked |
| readiness | object | key and label: Standby, Routine, Active, Elevated, Maximum. Driven by how many personal ships are out. |
| fleet_status | object | Fleet-wide rollup for drawing a status bar. Null for an empty fleet. Read fleet_status before you sum anything in it. |
| battle_groups | array | The clan's formations: id, name, tagline, logo_url, ship_count, commodores[]. Empty array when the clan has none. See Battle Groups. |
| ships | array | Alliance ships and auxiliaries. |
| personal_ships | array | Members' own ships. Same object shape, sorted by owner name. |
| crew | object | leaders, shipmasters, commodores, captains, first_officers |
| activity | array | Last 10 fleet events. |
| cache_seconds | int | How long a response stays warm. Currently 60. |
| Field | Type | Notes |
|---|---|---|
| id | int | Stable. Detail page is /hangar/ship/<id>. |
| name | string | The ship's name. |
| ship_class | string | Display name, e.g. "Super Destroyer". |
| class_key | string | Slug form. Null for auxiliary ships, which have no class. |
| origin_type | string | alliance or auxiliary. |
| is_personal | bool | True for a member's own ship (Super Destroyer). |
| owner_username | string | Set on personal ships (SDs), null on alliance ships. |
| battle_group | object | id and name of the formation this ship serves in, or null. Always null on personal ships, which cannot join one. |
| level | int | Ship level. |
| level_color | string | Hex color we use for the level badge. |
| xp | int | Total XP. |
| hull | object | segments (1 to 5), max_segments, label. See below. |
| condition | object | key, label, location, forced. See Hull Breaches for what forced means. |
| progress_pct | float | How far through a deployment or repair, 0 to 100. Null when nothing is running. |
| image_url | string | CDN thumbnail, or null. |
| awarded_at | string | When the clan got the ship. |
| awarded_for_opcode | string | Operation it was awarded for, or null. |
anchored, preparing_deploy, preparing_dss, deployed, escorting_dss, drydocked, reserve. You may also see supporting_operation on older ships; nothing new enters that state. Match on key, not label. Labels are display copy and can be reworded.
Those are the keys a ship reports. The condition_key inside fleet_status.by_condition is a different, smaller vocabulary: it carries preparing, which no individual ship ever reports, and it never carries reserve. Don't build one lookup table and use it for both.
This object exists to draw the status bar players see on the clan's hangar page, so it is shaped for that job rather than for arithmetic.
| Field | Type | Notes |
|---|---|---|
| total_ships | int | Alliance ships and auxiliaries. Matches counts.alliance_ships. |
| total_sds | int | Personal Super Destroyers. Matches counts.personal_ships. |
| total_all | int | total_ships + total_sds. The clan's whole fleet. |
| reserved_count | int | Ships in reserve, i.e. anchored more than 7 days. Still fully operational. |
| active_total | int | total_all minus reserved_count. This is what by_condition adds up to. |
| operational_pct | int | Percentage of active_total that is not drydocked. Not a percentage of the whole fleet. |
| by_condition | array | condition, condition_key, count, color. Display order, Reserve excluded, prep states merged. |
Two things are folded into it before you see it, and both will quietly cost you ships if you sum the array expecting a headcount.
Reserve is absent. Ships anchored more than seven days are pulled out so the bar reflects the active fleet, and they are large in number on established clans. One real example at the time of writing: a clan with 168 ships and 98 in reserve publishes a by_condition summing to 70. Add reserved_count if you want the fleet, or just use total_all.
The two prep states are merged. preparing_deploy and preparing_dss arrive as a single preparing segment. That key appears nowhere else in the API, so a colour or label map built from the ship condition keys above will meet a key it does not know.
If you want counts you control, group the ships and personal_ships arrays yourself on condition.key. Everything in fleet_status is derivable from them, and then the vocabulary is the one this page documents.
Hull is published as filled segments out of five, the same bar players see on the site. There is no raw percentage in this API and there won't be one.
Segments never reach zero. A wrecked ship reads 1, "Critical".
A breached ship and a ship its leadership sent for routine repairs both read "key": "drydocked". Hull segments won't separate them either, because a breach can fire without the ship crossing a segment boundary. There are two reliable signals instead.
Every ship's condition object carries forced. It is true only when a hull breach put the ship where it is, and false for every voluntary drydock.
"condition": {
"key": "drydocked",
"label": "Drydocked",
"location": "Freedom Alliance Orbital Hub",
"forced": true
}
Use this when you poll on a slow cycle, or when you want to show which ships are currently out of action through damage rather than by choice. It stays true for as long as the emergency repair lasts, so you cannot miss it.
A breach writes a distinct event with event_type of hull_breach. Watch for it if you want to announce breaches as they occur rather than report standing state.
for event in hangar["activity"]:
if event["event_type"] == "hull_breach":
print(event["ship_name"], "breached at", event["location"])
Bear in mind the feed only carries the last 10 events for the clan. A busy clan during an operation can push a breach out of that window between two polls, which is exactly why the forced flag exists alongside it. If you want both reliability and timeliness, watch the flag for state and the feed for the announcement.
A Battle Group is a named formation of a clan's ships that its leadership commands as one order. Clans use them to organise a fleet into task forces. The formation list arrives at the top level, and every ship tells you which one it belongs to, so you can present a clan's fleet grouped the way the clan itself groups it.
| Field | Type | Notes |
|---|---|---|
| id | int | Stable. Page is /hangar/battlegroup/<id>. |
| name | string | The formation's name. Unique within a clan. |
| tagline | string | Short subtitle, or null. |
| logo_url | string | CDN image, or null when the formation has no logo. |
| ship_count | int | Members visible in this response. See the note below. |
| commodores | array | Standard crew rows: username, public_id. |
Membership is a label, not a state. Every member keeps its own condition, so a formation of six can have three ships deployed, one in drydock and two anchored at the same time. There is no formation-level status in this API and there won't be one, because there isn't one in the game either. If you want to know what a task force is up to, read the condition of each ship whose battle_group.id matches.
ship_count counts what you can see. It is derived from the same ships array this response carries, not from the clan's raw membership. A ship on a class we haven't published yet is withheld from the array, and it is left out of the count too, so the two always agree. Group the ships array yourself and you will get the same numbers.
Super Destroyers can never join a formation, so battle_group is always null across personal_ships. The key is still present on every ship in both arrays, so you can walk either one without branching.
groups = {g["id"]: g for g in hangar["battle_groups"]}
for ship in hangar["ships"]:
bg = ship["battle_group"]
if bg:
print(bg["name"], ship["name"], ship["condition"]["label"])
Formations come and go. A disbanded Battle Group leaves the list immediately and its ships' battle_group goes null, so don't treat an id you saw yesterday as permanent. Key your own storage on the id while it is present, and drop it when it stops appearing.
Every crew list uses the same row shape: username and public_id. Captains and first officers add ship_id and ship_name; commodores add battle_group_id and battle_group_name. Profile links are /user/<public_id>.
Activity events carry ship_id, ship_name, class_display, event, event_type, condition_key, at (ISO 8601), location, location_kind, location_opcode, notes, and an actor of username plus role_label.
public_id is the only user identifier this API returns. Discord IDs are deliberately absent, and users who have been removed from public view won't appear in any list. Don't key your storage on username, because people rename. Key on public_id.
A clan that can't be shown still returns HTTP 200 with available: false and a reason. This is on purpose: you can keep a clan in your poll list through a disbandment without special-casing an error.
| Reason | Meaning |
|---|---|
| not_found | No such clan. |
| unavailable | The clan is disbanded, suspended, or delisted. It may come back. |
| hangar_closed | The hangar feature is closed site-wide. Temporary; retry later. |
Bad input returns HTTP 400 with an error code and a human-readable detail:
{"error": "invalid_id", "detail": "Clan IDs must be integers; got 'abc'.", "max_ids": 25}
{"error": "too_many_ids", "detail": "Requested 26 clans; the limit is 25 per call.", "max_ids": 25}
{"error": "missing_ids", "detail": "Pass ?ids= with a comma-separated list of clan IDs.", "max_ids": 25}
Over-length lists are rejected.
This API is read-only and returns only data already public on the site. It may be changed, updated, or switched off at any time. We make no uptime guarantees.
Need help? Support Server | Business: [email protected]
Built for the Freedom Alliance | freedomalliance.gg
Full clan index: all clans A-Z
HD2Clans is a fan-made community project and is not affiliated with, endorsed by, or associated with Arrowhead Game Studios AB or Sony Interactive Entertainment. HELLDIVERS is a trademark of Sony Interactive Entertainment LLC. All game content and assets are property of their respective owners.