# packs.haruhime.moe docs, guides and legal pages

> Build osu! tournament mappool packs from beatmap IDs or links, download them as one zip or a torrent, and share them with a pack key or a short link.

---

# API

Source: https://packs.haruhime.moe/docs/api

The packs API lets your own scripts and bots read public packs and manage the packs you saved. It's JSON over HTTPS at `https://packs.haruhime.moe/api/v1`, and every request needs your personal API key.

A machine-readable description is at [/api/v1/openapi.json](https://packs.haruhime.moe/api/v1/openapi.json) (OpenAPI 3.1).

## Quick start

1. Sign in with osu! and open [your account page](https://packs.haruhime.moe/me).
2. In **API key**, press **Create API key**. Copy the key right away: you only see it once.
3. Call the API with it:

```sh
curl https://packs.haruhime.moe/api/v1/me \
  -H "Authorization: Bearer hpk_your_key_here"
```

```json
{ "user": { "id": "66f0a1b2c3d4e5f6a7b8c9d0", "osuId": 1234567, "username": "player1" } }
```

## Authentication

- Send `Authorization: Bearer hpk_…` with every request. A key is `hpk_` followed by 43 letters, digits, `-` and `_`.
- Each account has one key. **Regenerate** on your account page makes a new one, and the old key stops working right away. **Revoke** deletes it.
- We keep only a hash of your key, so we can't show it to you again. Lost it? Regenerate.
- A key acts as you. It can't hide, pin or moderate packs, not even an admin's key.
- A missing key gets `401` with the code `unauthorized`. A wrong, revoked, or replaced key gets `401` with the code `invalid_api_key`.

### Keep your key on a server

The API is for servers and bots. It sends no CORS headers, so a web page on another site can't call it, and a key inside a web page or app would leak to everyone who opens it. Keep it in an environment variable or a secret store, never in a public repo.

## Rate limits

- 60 requests a minute per account, across every endpoint.
- 10 writes a minute per account (`POST`, `PUT`, `DELETE`). Writes count toward the 60 too. Saving, editing or deleting packs and magnet links on the site counts toward the same 10.
- 20 failed key attempts a minute per IP address (an IPv6 address counts by its /64).
- 10 new keys an hour per account, on your account page.

Counters start over at the top of each minute (each hour for new keys). Every response carries:

- `RateLimit-Limit`: requests allowed in the current window;
- `RateLimit-Remaining`: how many are left;
- `RateLimit-Reset`: seconds until the window starts over.

On a 401, these describe the failed-attempt limit for your IP address.

Over a limit you get `429` with a `Retry-After` header, in seconds. Wait that long, then try again.

## Errors

Every error has the same shape:

```json
{ "error": { "code": "not_found", "message": "Pack not found." } }
```

`code` doesn't change, so your program can check it. `message` is for people and may change.

- `400` `bad_request`: the body or `page` isn't valid. The message says what to fix.
- `401` `unauthorized`: no key. A bad key gets `invalid_api_key` instead. Both come with `WWW-Authenticate: Bearer`.
- `404` `not_found`: the pack doesn't exist, or it's private or hidden and not yours. `PUT` and `DELETE` also answer `404` for any pack that isn't yours. The API doesn't say which.
- `409` `conflict`: you already have 200 saved packs. Delete one first.
- `413` `too_large`: the body is over 16 KB.
- `415` `unsupported_media_type`: send the body with `Content-Type: application/json`.
- `429` `rate_limited`: see Rate limits.
- `500` `internal_error`: something failed on our side. Try again later.

## The pack object

```json
{
  "slug": "V1StGXR8_Z",
  "name": "Spring Cup Finals",
  "visibility": "public",
  "description": "Grand finals pool.",
  "slots": [
    { "mod": "NM", "index": 1, "beatmapId": 129891 },
    { "mod": "HD", "index": 1, "beatmapId": 75 }
  ],
  "exports": [],
  "stats": {
    "srMin": 2.55,
    "srMax": 7.81,
    "srAvg": 5.18,
    "lenMin": 142,
    "lenMax": 258,
    "bpmMin": 120,
    "bpmMax": 222,
    "mods": ["NM", "HD"],
    "modes": ["osu"],
    "count": 2,
    "complete": true,
    "computedAt": "2026-09-22T12:00:05.000Z"
  },
  "packKey": "pk1.…",
  "ownerName": "player1",
  "createdAt": "2026-09-22T12:00:00.000Z",
  "updatedAt": "2026-09-22T12:00:00.000Z"
}
```

- `slots` holds beatmap (difficulty) IDs by slot. Titles, star ratings per map, and other beatmap details aren't included. Look them up on osu! or a beatmap mirror.
- `buckets` shows up when a pack has its own slots or slot order. Custom slots carry their color and mods.
- `description` is left out when the pack has none.
- `ownerName` is the owner's osu! username, `haruhime pools` on the tournament pools pools.haruhime.moe publishes, or `Unknown player` when we don't have one.
- `packKey` is the pack's [pack key](https://packs.haruhime.moe/guides/pack-key). Anyone can open it at `https://packs.haruhime.moe/k#` followed by the key.
- `exports` lists the magnet links the owner recorded, newest first.
- `hiddenAt` shows up only on your own packs, when a moderator has hidden one.
- `stats` sums up the pack's maps. We work it out a few seconds after each save, so it's missing from the answer to `POST` and to a `PUT` that changes the maps or slots; read the pack again a little later. It's also missing for a pack whose stats we haven't worked out yet.

### Pack stats

- `srMin`, `srMax`, `srAvg`: star rating, 2 decimals. A slot that forces EZ, HR, DT, HT or FL counts with its rating with those mods; every other slot (NM, HD, FM, TB, free mod) counts with the plain rating.
- `lenMin`, `lenMax`: map length in seconds, and `bpmMin`, `bpmMax`: BPM, both after DT (1.5 times as fast) and HT (0.75 times).
- `mods`: the pack's built-in slots (`NM`, `HD`, `HR`, `DT`, `FM`, `TB`) and the mods its custom slots force (`EZ`, `HD`, `HR`, `DT`, `HT`, `FL`; a custom free mod slot counts as `FM`), in that order.
- `modes`: the rulesets of its maps (`osu`, `taiko`, `fruits`, `mania`).
- `count`: the number of maps.
- `complete`: `false` when we couldn't look up a map or a rating with mods. The numbers then cover the maps we could, and we try again later. A map osu! says doesn't exist (deleted, say) is left out of the numbers and doesn't make `complete` false. A range is `null` when no map gave a value.
- `computedAt`: when we worked the stats out.

## Endpoints

### `GET /api/v1/me`

The key's owner.

```json
{ "user": { "id": "66f0a1b2c3d4e5f6a7b8c9d0", "osuId": 1234567, "username": "player1" } }
```

### `GET /api/v1/packs`

Public packs, most recently updated first, 50 per page. Takes `?page=` (see Pagination). Each entry in `packs` is a full pack object, shortened here. Pinned packs aren't marked or moved up here; the Pinned row is only on the site.

```json
{ "packs": [{ "slug": "V1StGXR8_Z", "name": "Spring Cup Finals", "packKey": "pk1.…" }], "page": 1, "pageCount": 3, "total": 131 }
```

### `GET /api/v1/packs/{slug}`

One pack. Public and unlisted packs work with any key. Private and hidden packs work only with their owner's key.

```json
{ "pack": { "slug": "V1StGXR8_Z", "name": "Spring Cup Finals", "packKey": "pk1.…" } }
```

### `GET /api/v1/me/packs`

Your packs, any visibility, most recently updated first, 50 per page. Takes `?page=` (see Pagination) and answers in the same shape as `GET /api/v1/packs`.

```json
{ "packs": [{ "slug": "V1StGXR8_Z", "name": "Spring Cup Finals", "visibility": "private" }], "page": 1, "pageCount": 1, "total": 12 }
```

### `POST /api/v1/packs`

Save a new pack. The body follows the same rules as the site: a name of 1 to 64 characters, 1 to 64 maps, a description of up to 500 characters, up to 8 custom slots, and no slurs in the name, the description or custom slot names. `visibility` is `private`, `unlisted` (the default), or `public`.

```sh
curl https://packs.haruhime.moe/api/v1/packs \
  -H "Authorization: Bearer hpk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"name":"Spring Cup Finals","visibility":"unlisted","slots":[{"mod":"NM","index":1,"beatmapId":129891}]}'
```

Answers `201` with `{ "pack": … }`.

### `PUT /api/v1/packs/{slug}`

Replace one of your packs. Send the whole pack, as for `POST`. Leaving out `description` clears it, and leaving out `visibility` makes the pack unlisted. A new pool (different maps, slots, or name) clears the pack's recorded magnet links, because they no longer match. A pack a moderator hid stays hidden, and a pinned pack that stops being public loses its pin. Answers `200` with `{ "pack": … }`.

### `DELETE /api/v1/packs/{slug}`

Delete one of your packs. Answers `204` with no body.

## Pagination

`page` starts at 1 and goes up to 999999. Responses include `page`, `pageCount`, and `total`. A page past the end comes back with an empty `packs` list.

To list public packs without a key, use the static search index at [/packs/index.json](https://packs.haruhime.moe/packs/index.json) (up to 5,000 packs). It doesn't count toward any limit, and it updates shortly after a public pack changes. Entries are newest created first. Each entry has `s` (slug), `n` (name), `o` (the owner's osu! username), `c` (map count), `d` (the start of the description: up to 140 characters, plus `…` when it's cut), `u` (last updated) and `t` (created). Each entry also carries the pack's stats in short form once we have them: `r` star rating range, `a` average stars, `l` length range in seconds, `b` BPM range, `m` mods and `g` rulesets (comma-separated), and `k` for `complete`. For a pack's maps, call `GET /api/v1/packs/{slug}`.

## Pack keys

A pack key holds a whole pool in one line of text. The [pack key guide](https://packs.haruhime.moe/guides/pack-key) documents every key version, byte by byte.

## Use it with Claude Code

The haruhime plugin for Claude Code has skills for osu! tools, including one for packs and this API. Install it from Claude Code:

```
/plugin marketplace add haruhimemoe/claude-plugin
/plugin install haruhime@haruhimemoe
```

The source is at [github.com/haruhimemoe/claude-plugin](https://github.com/haruhimemoe/claude-plugin).

## Help

Questions about the API, or something not working as this page says? Ask in our [Discord server](https://haruhime.moe/discord).

## Changes

- 2026-09-24: removed map usage (`GET /beatmaps/{id}/usage` and `GET /beatmaps/usage`), the `archive` field on pack objects, the index keys `x`, `xk` and `xu`, and the `source` filter on /packs (`source=`). They were live for about a day. Tournament pools from pools.haruhime.moe are plain packs owned by `haruhime pools`.
- 2026-09-24: `GET /beatmaps/{id}/usage` and `GET /beatmaps/usage` listed the archive pools a map was used in, until the removal above. They needed no key.
- 2026-09-24: archive packs (past tournament pools) carried `archive`, and their index entries carried `x`, `xk` and `xu`, until the removal above. The index listed them after community packs.
- 2026-09-24: pack objects carry `stats`, and the search index carries them in short form. Index entries carry `t` (created) and are newest created first.
- 2026-09-23: `GET /me/packs` is paged like `GET /packs`.

---

# How to make an osu! mappool pack

Source: https://packs.haruhime.moe/guides/make-a-pack

To make an osu! mappool pack, open the builder at [packs.haruhime.moe/new](https://packs.haruhime.moe/new), paste your beatmap IDs or links, and download the pool as one zip. It's free, and you don't need an account. It takes about a minute.

## Steps

1. **Open the builder.** Go to [New pack](https://packs.haruhime.moe/new). Your draft saves in this browser as you go, so you can close the tab and come back.
2. **Name the pack.** Type the tournament and round, for example `Spring Cup 2026 Quarterfinals`. The name goes on the zip and the folder inside it.
3. **Paste the pool.** Paste one line per map into "Paste a mappool" and press "Add to pool". All of these work:
   - bare IDs, such as `129891`, or several on one line separated by commas or spaces;
   - slot lines copied from a spreadsheet, such as `NM1 129891` or `HD2 https://osu.ppy.sh/beatmapsets/39804#osu/129891`;
   - your own slots, such as `EZ1 1872396`, which creates an EZ slot.

   Lines with only IDs become maps without a slot. Lines the builder can't read stay in the box with a note.
4. **Arrange the slots.** In "Slots", drag slots into your order, add your own (up to 8, each with a color), or rename them. Within a slot, drag a map by its handle to reorder it (NM1, NM2, ... renumber to match); to move a map to another slot, pick the slot in its row and press "Move".
5. **Download the zip.** In the "Download" card, press "Download maps", then "Save .zip". Your browser fetches each beatmap set from the mirror and builds one zip, with files numbered in pool order and a `pack.txt` listing every map.
6. **Share it.** Copy the pack key from "Share" and post it anywhere. Anyone who pastes it on the homepage gets the same pool. Sign in with osu! and press "Save to account" to get a short link like `packs.haruhime.moe/p/abcdefghij`, then set it to Public to list it on [public packs](https://packs.haruhime.moe/packs).

## Tips

- A pack holds up to 64 maps.
- Every map row has a "Copy ID" button that copies its beatmap ID, ready for `!mp map` in a tournament lobby.
- Players can open a shared key and download the same zip themselves.
- [Pack keys](https://packs.haruhime.moe/guides/pack-key) explains what a key contains.
- To share the pool as a torrent, see [how to seed an osu! mappool torrent](https://packs.haruhime.moe/guides/seed-a-torrent).
- To put the pack's maps in one of your osu! collections, see [Add a pack to your osu! collections](https://packs.haruhime.moe/guides/osu-collections).

---

# Add a pack to your osu! collections

Source: https://packs.haruhime.moe/guides/osu-collections

To add a pack to an osu! collection, open the pack on packs and use the "Add to osu! collection" card under its maps. On osu!stable, you load your `collection.db` and download it back with the pack's maps added. On osu!lazer, you download a small zip and import it with lazer's setup wizard. Your `collection.db` is read in your browser: packs doesn't upload it or keep it.

## What a collection holds

An osu! collection is a name and a list of difficulties. osu! doesn't list them by beatmap ID: each difficulty is the MD5 checksum of its `.osu` file. packs takes those checksums from the map info it already loads for the pack, so it doesn't need your maps or your osu! account.

A difficulty you haven't downloaded yet can still go in a collection. osu! hides it until you have the map, then shows it. The card lists any map it can't add: maps the mirror and osu! don't know, and maps whose info has no checksum.

## osu!stable

1. **Close osu!.** osu!stable saves collection changes a while after you make them, or when it closes, so the file on disk can be behind until then. Keep it closed until the new file is in place.
2. **Find your collection.db.** It's in your osu! folder, next to `osu!.db`. On Windows that's `%LOCALAPPDATA%\osu!` unless you installed osu! somewhere else. The AppData folder is hidden, so paste `%LOCALAPPDATA%\osu!` into the file picker's address bar to get there. No `collection.db` yet? Make any collection in osu!, close osu!, then load the file it writes.
3. **Load it.** On the pack's page, in "Add to osu! collection", pick osu!stable and choose the file.
4. **Pick a collection.** Choose one you have, or make a new one. A new one is named after the pack, and you can change the name. Typing the exact name of a collection you have adds to that one. Case counts, and so do spaces inside the name.
5. **Check the preview.** It says how many maps get added and how many are already in the collection.
6. **Download collection.db.** The download holds all your collections, with this pack's maps added. Nothing else changes, except that empty map entries are left out if the card found any.
7. **Swap the file.** Keep a copy of your old `collection.db`, put the new one in the osu! folder named exactly `collection.db`, and start osu!.

osu! has to stay closed from the moment you load the file until the new one is in place. osu!stable reads `collection.db` when it starts and saves its own copy later, so it ignores a file swapped while it runs and then writes over it, and a file loaded while it runs can miss your latest changes. If your browser saved the download as `collection (1).db`, rename it to `collection.db`.

If you have more collections than the card lists, pick "New collection" and type the exact name of the one you want: the card adds to it.

To add the pack to a second collection, pick it and download again. The second download includes the first change, so only the last file needs to go in the osu! folder.

## osu!lazer

osu!lazer keeps collections in its own database, which a web page can't open, and it can't export them. It can import a `collection.db` through its setup wizard, and it merges what it imports into the collection with the same name. So packs gives you a file with just this pack's maps.

1. **Name the collection.** In "Add to osu! collection", pick osu!lazer and type the collection's name exactly as it shows in lazer. Case and spaces count: any other name makes a new collection.
2. **Download the zip.** Press "Download zip for osu!lazer".
3. **Extract it.** You get one folder holding `collection.db` and an empty `osu!.import.cfg`. The empty file is what makes lazer accept the folder.
4. **Open the setup wizard.** In osu!lazer, open Settings, then General, and press "Run setup wizard". Press Next until you reach the Import step.
5. **Choose the folder.** Pick the extracted folder as the previous osu! install, or drag the folder onto the osu! window. If osu!stable is installed, the field already points at it: change it.
6. **Import only collections.** Untick Beatmaps, Scores and Skins, leave Collections ticked, and press "Import content from previous version". Wait until lazer says it imported the collections.

An import only adds. Nothing already in lazer is removed or renamed, and importing the same zip twice adds nothing the second time. This works on desktop: osu!lazer on Android and iOS can't import collections.

## When a map doesn't show up

- **You don't have it yet.** Download the pack. The map shows up in the collection once osu! has it.
- **The map was updated.** A collection points at one exact version of a difficulty. packs uses the checksum osu! lists today, so a copy you downloaded before an update, or an older copy on the mirror, may not match. Update the map in osu!, and it shows up.
- **packs left it out.** The card lists the maps it couldn't add, and why.

## Your file

packs reads your `collection.db` in your browser tab. It isn't uploaded, saved or logged, and it's gone when you close the tab.

---

# Download a pack with a torrent

Source: https://packs.haruhime.moe/guides/download-a-torrent

To download a pack with a torrent, open the pack's magnet link in a torrent app like qBittorrent and pick a folder to save it in. The app downloads the maps from other players who are seeding the pack. When it's done, open the `.osz` files to import them into osu!.

## What a magnet link is

A magnet link is a short link that names a torrent's files by their fingerprint. Your torrent app uses it to find people who have those files and download from them. packs only shows the link: the files come from other players' computers.

When you open a magnet link, your torrent app contacts the trackers in the link and other peers, and they see your IP address. That's how torrents work. packs only lists its own set of trackers in magnet links.

## Steps

1. **Get a torrent app.** Install [qBittorrent](https://www.qbittorrent.org). It's free and open source.
2. **Open the magnet link.** On the pack's page, press "Open" next to a magnet link, and your torrent app picks it up. Or copy the link and add it in the app (in qBittorrent, "File", then "Add Torrent Link").
3. **Pick a download folder.** Choose where the pack goes and start the download.
4. **Wait for it to finish.** A torrent only downloads while someone is seeding it. If it stalls at 0% or stops moving, download the maps from the mirror on the same pack page instead.
5. **Import the maps.** A pack made on packs is a folder of numbered `.osz` files plus `pack.txt`. Double-click each `.osz`, or drag them onto osu!, to import them. If a torrent has anything else in it (an `.exe`, a script, a shortcut), don't open it: delete it and use the mirror download instead.
6. **Seed it back.** Leave the torrent running for a while so the next player can download it too. [How to seed a torrent](https://packs.haruhime.moe/guides/seed-a-torrent) covers the details.

Only download and share what you're allowed to where you live.

---

# How to seed an osu! mappool torrent

Source: https://packs.haruhime.moe/guides/seed-a-torrent

To seed an osu! mappool torrent, make the torrent on your pack's page, unzip the same pack's zip, and open the `.torrent` in qBittorrent with the unzipped folder's parent as the save location. qBittorrent finds every file already there and starts seeding. packs builds the torrent in your browser; your computer does the sharing.

## Steps

1. **Download the maps.** Open your pack ([New pack](https://packs.haruhime.moe/new), a pack key, or a saved pack) and in the "Download" card press "Download maps". Wait until every map is ready.
2. **Save and unzip the zip.** Press "Save .zip" and unzip it. You get one folder named after the pack, for example `Spring Cup 2026 Quarterfinals`, holding the numbered `.osz` files and `pack.txt`.
3. **Make the torrent.** Press "Make torrent". Your browser fingerprints every file, which takes a few seconds for a small pack and up to a minute for a big one. Then press "Save .torrent" and copy the magnet link.
4. **Open it in qBittorrent.** Open the `.torrent` file. Set the save location to the folder that holds the pack folder (not the pack folder itself), and keep "Skip hash check" off. qBittorrent checks the files, shows 100%, and starts seeding.
5. **Share the magnet link.** Post the magnet link or the `.torrent` file wherever your players are. On a saved pack, press "Add magnet link to this pack" so it shows on the pack page for everyone.
6. **Keep seeding.** Leave qBittorrent running until players have the pack. Every player who finishes can seed it too.

To download a pack someone else is seeding, see [Download a pack with a torrent](https://packs.haruhime.moe/guides/download-a-torrent).

## Tips

- Make the zip and the torrent in the same browser, right after each other. They come from the same downloaded files, so they match exactly.
- If you change the pack's name or maps, make a new torrent. The old one no longer matches, and a saved pack drops its old magnet links when you save those changes.
- If the mirror updates a map later, a new torrent of the same pack gets a different magnet link.
- Changing the download options (videos, backgrounds) gives different files, so a different torrent and magnet link.
- Some networks block torrents. If nobody can connect, try seeding from another network.
- Browser torrent apps can join through the WebSocket trackers, but desktop apps like qBittorrent are the reliable choice.

---

# Pack keys

Source: https://packs.haruhime.moe/guides/pack-key

A pack key is a short piece of text that holds a whole mappool: the pack name, which beatmap goes in which slot, and the mods custom slots are played with. Paste it into packs and you get the same pool back, anywhere, without an account. Keys start with `pk1.`, `pk2.` or `pk3.`.

## Sharing a pack

- On the builder, use **Copy key** or **Copy share link**.
- A share link looks like `https://packs.haruhime.moe/k#pk1.…`. Everything after `/k#` is the key. Browsers never send that part to our server.
- To open a key, paste it (or a message containing it) into the box on the home page.

A key holds IDs only. Titles, difficulty names, and stats are looked up again when the key is opened, so a key never goes stale, but a map that was updated on osu! shows its current version.

## Version 1: the original format

A key is `pk1.` followed by base64url text (letters, digits, `-` and `_`, no padding). Decoded, the bytes are:

1. **Version**: one byte, `1`.
2. **Name length**: a varint (unsigned LEB128), then the name as UTF-8. Names are 1 to 64 characters.
3. **Slot count**: a varint, at most 64.
4. **Each slot**: one byte for the mod bucket, then the slot number as a varint, then the beatmap (difficulty) ID as a varint. Bucket bytes are 0 to 5 for NM, HD, HR, DT, FM, TB, in that order.
5. **Checksum**: two bytes, CRC-16/CCITT-FALSE of everything before it, high byte first.

Slots are always written in bucket order and then slot number, so the same pool always makes the same key. A key that was cut off or mistyped fails the checksum, and packs tells you it's damaged instead of opening the wrong pool.

## Version 2: custom slots and maps without a slot

Packs that use only NM, HD, HR, DT, FM, and TB in that order, with every map in a slot, still get `pk1.` keys. A pack with its own slots (like `EZ` in green), a different slot order, or maps without a slot gets a `pk2.` key instead. Decoded, a `pk2.` key is:

1. **Version**: one byte, `2`.
2. **Name**: a varint length, then the name as UTF-8, as in version 1.
3. **Slot table**: a varint count (6 to 14), then each slot in pool order. A built-in is one byte, `0` to `5` for NM, HD, HR, DT, FM, TB. A custom slot is the byte `0xFE`, one color byte (`0` to `9`: green, teal, pink, lime, cyan, fuchsia, yellow, red, indigo, stone), then its code as a varint length and UTF-8. Codes are 1 to 12 letters or digits.
4. **Map count**: a varint, at most 64.
5. **Each map**: one byte for its position in the slot table, or `0xFF` for "no slot", then the slot number and the beatmap ID as varints.
6. **Checksum**: two bytes, CRC-16/CCITT-FALSE of everything before it, as in version 1.

Maps without a slot come first, numbered 1, 2, 3, then the slots in table order. The same pool always makes the same key.

## Version 3: mods on custom slots

A custom slot can set the mods its maps are played with: forced mods (like `EZ`, or `HD` and `DT` together) or freemod. That changes the pool, so it goes in the key. A pack gets a `pk3.` key only when at least one custom slot has mods. Every other pack keeps its `pk1.` or `pk2.` key, byte for byte. Decoded, a `pk3.` key is:

1. **Version**: one byte, `3`.
2. **Name**: as in version 2.
3. **Slot table**: as in version 2, with one more kind of entry. A custom slot with mods is the byte `0xFD`, one color byte, its code as a varint length and UTF-8, then a mode byte: `1` for forced mods or `2` for freemod. Forced mods add one more byte, the mod bitmask. A custom slot without mods is still written with `0xFE`.
4. **Map count** and **each map**: as in version 2.
5. **Checksum**: as in versions 1 and 2.

The mod bitmask adds up one bit per forced mod:

- `EZ` (Easy): 1
- `HD` (Hidden): 2
- `HR` (Hard Rock): 4
- `DT` (Double Time): 8
- `HT` (Half Time): 16
- `FL` (Flashlight): 32

So `HD` and `DT` together is `10`. A slot forces 1 to 3 mods. `EZ` with `HR`, and `DT` with `HT`, can't be forced together. Nightcore plays like `DT`, so use `DT`. Built-in slots never store mods: NM has none, HD, HR and DT force that mod, and FM and TB are freemod.

A `pk3.` key whose custom slots end up with no mods still opens fine, and the next time packs writes that pool's key, it comes back out as `pk1.` or `pk2.` again.

## Version history

- **pk1.** (2026-09-22): the first format. The pack name and its maps in the six built-in slots.
- **pk2.** (2026-09-22): custom slots, a changed slot order, and maps without a slot.
- **pk3.** (2026-09-23): mods on custom slots.

Every new key format gets its own section on this page and a line in this list.

---

# Terms of Service

Source: https://packs.haruhime.moe/legal/terms

These terms cover your use of packs.haruhime.moe ("the service", "we", "us"). By using the service you agree to them. If you don't agree, don't use it.

## What the service is

The service is a tool. It helps you look up osu! beatmaps, organize them into packs, and package them on your own device as a zip or a torrent. It also lists packs that others made public, including tournament pools published from pools.haruhime.moe, which can have mistakes.

**We do not host, store, cache on our servers, or distribute beatmaps, audio, images, videos, or any other files.** When you download or export a pack, your browser fetches the files directly from a third-party mirror ([mirror.hinamizawa.ai](https://mirror.hinamizawa.ai)) and assembles them locally.

## Your responsibility for content

Beatmaps usually contain copyrighted music, artwork, and video owned by third parties. You are **solely responsible** for:

- having the rights or permission needed to download, copy, share, seed, or upload any content you use with the service;
- everything you do with packs, pack keys, share links, and torrents, including who you share them with;
- following the terms of every third-party service involved, including osu! (ppy Pty Ltd), the hinamizawa beatmap mirror, and any torrent tracker.

Pack keys and torrents are created and controlled by you. We have no control over them and no responsibility for them once they exist.

### Magnet links

Pack owners are responsible for the magnet links they add to their packs. We don't host or seed the files a torrent points to, and we aren't responsible for what a torrent contains or for anyone's use of it. We may remove magnet links, or packs, that are reported to us or that break these terms.

## Accounts

You can sign in with your osu! account, on [haruhime.moe](https://www.haruhime.moe), the account every haruhime tool shares, to save packs. Keep your osu! account secure; you are responsible for activity under it. We may suspend or delete accounts or saved packs at our discretion, including in response to a copyright notice. We may hide or delete public or unlisted packs that break these terms, and we may pin public packs to the top of the public packs page or unpin them. We never review private packs.

## Acceptable use

Don't use the service to break the law, infringe anyone's rights, harass anyone, or interfere with the service or the third-party services it relies on (for example by automating requests to get around rate limits).

## API

You can use the packs API with a personal API key from your account page. When you do, these rules apply too:

- **Keys are personal.** Don't share your key or put it anywhere others can read it, such as a web page, an app, or a public repository. You're responsible for everything done with your key.
- **Fair use.** Stay within the rate limits in the [API docs](https://packs.haruhime.moe/docs/api), and don't try to get around them, for example with several accounts or keys.
- **We may revoke any key** that is used to abuse the service or its rate limits, or to break these terms, without notice.
- **No warranty.** The API is part of the service and comes "as is", like the rest of it. It may change, slow down, or stop, and we don't promise it stays compatible.

## No affiliation

The service is not affiliated with or endorsed by ppy Pty Ltd or hinamizawa (mirror.hinamizawa.ai). osu! is a trademark of ppy Pty Ltd.

## Disclaimer of warranties

packs.haruhime.moe is provided "as is" and "as available", without warranties of any kind, express or implied, including merchantability, fitness for a particular purpose and non-infringement.

## Limitation of liability

To the fullest extent the law allows, haruhime.moe is not liable for any indirect, incidental, special, consequential or punitive damages, or for any loss of data or accounts, arising from your use of packs.haruhime.moe.

Third-party mirrors and APIs can be slow, wrong, or unavailable, and we don't guarantee any file you get through the service is complete, current, or safe. Our total liability for any claim is limited to zero US dollars, since the service is free.

## Indemnity

You agree to defend, indemnify, and hold us harmless from any claim, loss, or expense (including reasonable legal fees) arising from your use of the service, content you download, share, seed, or upload, or your breach of these terms.

## Changes

We may update this page. It was last updated on 2026-10-06.

Continuing to use the service after an update means you accept it.

## Governing law

These terms are governed by the laws of the State of California, USA, without regard to conflict-of-law rules.

## Contact

haruhime.moe runs packs.haruhime.moe. For anything on this page, write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

---

# Privacy Policy

Source: https://packs.haruhime.moe/legal/privacy

This policy explains what packs.haruhime.moe collects, why, and what stays on your own device.

**[Your rights under GDPR and CCPA](https://packs.haruhime.moe/legal/your-privacy-rights)** have their own page, with a table of what we hold, why, and how long we keep it.

## What we store

Sign-in happens on [haruhime.moe](https://www.haruhime.moe), the account every haruhime tool shares. It stores your osu! user ID, username, avatar URL, and country, a record linking your account to osu! (never your osu! tokens), and the sessions that keep you signed in, with the IP address and browser User-Agent each one started from. haruhime.moe's own privacy page covers them. packs only reads that account and session to know who you are, and writes nothing to it.

Only if you sign in, packs itself stores:

- packs you choose to save: the pack name, description, beatmap IDs, slot layout, visibility, and any magnet links you choose to record on a pack (we keep only the torrent's fingerprint, name, size, and our own tracker list).
- every saved version of a pack (its name, description, beatmap IDs and slot layout, with who saved it and when), kept until the pack is deleted. Only you see it unless you turn on "Anyone who can see the pack" for that pack's history.
- If you create an API key: a SHA-256 hash of it (never the key itself), its first 12 characters so you can recognize it, when you created it, and when it was last used (updated at most once an hour).
- If you save or change packs (magnet links included), use the API, or create a key: short-lived request counters, by account, to enforce rate limits. They're deleted automatically within about two minutes, or about an hour for key creation.
- If you delete your packs data: a counter of deletions, by osu! user ID, deleted automatically within about an hour.
- If you set a pack to Public, it's listed on the public packs page with your osu! username and avatar.

If you don't sign in, we don't keep an account or any profile about you. If you send a request to the API without a valid key, we keep your IP address in a short-lived counter (about 2 minutes) to stop key guessing. Our host (Vercel) keeps standard request logs, such as IP address and browser type, for security and operations.

## Your IP address

Every place packs uses your IP address:

- **Sign-in sessions.** haruhime.moe stores the IP address you signed in from with each session, as listed above. packs never stores it.
- **osu! lookups.** While you build or view a pack, signed in or not, your browser asks our server for star ratings with mods (`/api/osu/star-ratings`) and for maps the mirror doesn't have (`/api/osu/beatmaps`). Our server counts these requests per IP address in short-lived counters, so one visitor can't use up the site's osu! API quota. When you save a pack, on the site or through the API, our server looks up its maps to work out the pack's stats, and any osu! calls it makes for that count against your IP address's share too. The counters are deleted automatically within about two minutes.
- **API requests without a valid key.** Counted per IP address for about 2 minutes, to stop key guessing.
- **Requests to our pools service without its token.** Only pools.haruhime.moe has the token for the endpoints it publishes packs through. A request to them without the token is counted per IP address for about 2 minutes, and an address that keeps failing is told to slow down.
- **Our host's request logs.** Vercel keeps standard request logs, including your IP address.

Every counter groups an IPv6 address by its /64 network, so the addresses in one network share a counter. Our server never forwards your IP address to osu!: the osu! API sees only our server.

## What we never store

- **Files.** No beatmaps, audio, images, or videos ever pass through or rest on our servers.

## Cookies

packs sets no cookies of its own. It reads two that haruhime.moe sets on `.haruhime.moe` when you sign in there:

- **The session cookie** keeps you signed in. It's HttpOnly, so page scripts can't read it.
- **`haruhime-signed-in`** tells the page to check whether you're signed in. Page scripts can read it, and it holds no personal data.

Signing out on packs asks haruhime.moe to end the session and clears both cookies.

We use no tracking or advertising cookies, so there's no cookie banner.

## Requests your browser makes to others

When you build or export a pack, your browser requests beatmap data and files directly from the beatmap mirror (mirror.hinamizawa.ai), cover images from osu!'s CDN (assets.ppy.sh), and player avatars from osu! (a.ppy.sh). Those services see your IP address and browser details, under their own policies.

Magnet links on a pack page were added by the pack's owner. If you open one, your torrent app contacts the trackers listed in it and other peers, and they see your IP address: that's how torrents work. packs lists only its own set of public trackers in these links.

## Requests our server makes

When the mirror doesn't have a map, your browser asks our server, and our server asks the osu! API (osu.ppy.sh) for that map's details. Only beatmap IDs are sent to osu!, never anything about you. To show star ratings with mods, our server also sends osu! a beatmap ID and the mod names (like HD or DT), and nothing else.

After a pack is saved, our server works out its stats (star rating, length and BPM ranges), and a daily job fills in what it couldn't get. It asks the beatmap mirror (mirror.hinamizawa.ai) for the maps' details, and osu! for maps the mirror doesn't have and for ratings with mods. These requests carry beatmap IDs and mod names only, and our server never downloads beatmap files.

So a saved pack's page can list its maps before your browser has loaded anything, our server also asks the mirror for the maps' details when it builds that page (at most about once a day per pack, not once per visitor). That request carries beatmap IDs only.

## Data on your device

Pack drafts are kept in your browser's IndexedDB, and downloaded beatmap files are cached in your browser's private file storage so a second export doesn't download everything again. Your download options (videos and backgrounds) are saved in this browser's local storage, and removing backgrounds happens on your device too. When you filter public packs, this tab's session storage remembers how many results were showing and how far down you'd scrolled, so the Back button takes you there again. It's gone when you close the tab. This data stays on your device. You can delete it anytime with the "Clear local data" button at the bottom of every page, or by clearing site data in your browser.

## Service providers

- **[Vercel](https://vercel.com)** hosts the website and runs its server functions.
- **[MongoDB Atlas](https://www.mongodb.com/atlas)** stores the pack data listed above.
- **[haruhime.moe](https://www.haruhime.moe)** runs sign-in and keeps the account and sessions packs reads to know who you are.
- **[osu! (ppy Pty Ltd)](https://osu.ppy.sh)** confirms sign-in for haruhime.moe and answers lookups for maps the mirror doesn't have.

We don't use analytics or advertising trackers, and we don't sell personal data.

## How long we keep it

The [GDPR & CCPA page](https://packs.haruhime.moe/legal/your-privacy-rights) has a table of each kind of data we hold, why, and how long we keep it.

## Getting or deleting your data

**Download my data** on your [packs settings page](https://packs.haruhime.moe/me) gives you a copy of everything packs holds about you, as a JSON file. **Delete my packs data** on the same page deletes your API key and every pack you saved, along with its saved history. Your haruhime account itself (your user record, sessions, and linked osu! account record) is deleted on [haruhime.moe/account](https://www.haruhime.moe/account); delete your packs data first if you want it gone too. You can also email us to ask for a copy or for deletion.

## Changes

We may update this page. It was last updated on 2026-10-06.

## Contact

haruhime.moe runs packs.haruhime.moe. For anything on this page, write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

---

# GDPR & CCPA

Source: https://packs.haruhime.moe/legal/your-privacy-rights

This page explains your rights over your personal data under the GDPR (EU and UK) and the CCPA (California, as amended by the CPRA), and how to use them. The [Privacy Policy](https://packs.haruhime.moe/legal/privacy) has the full picture of what packs collects.

## Who's responsible

The operator of packs.haruhime.moe is the controller of your personal data. Write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe) about anything on this page.

## What we hold and why

<table>
  <thead>
    <tr>
      <th>Data</th>
      <th>Purpose</th>
      <th>GDPR legal basis</th>
      <th>Kept until</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Your osu! user ID, username, avatar URL, and country, read from your haruhime.moe account</td>
      <td>Knowing who you are and which packs are yours</td>
      <td>Contract</td>
      <td>haruhime.moe keeps it until you delete your haruhime account there</td>
    </tr>
    <tr>
      <td>Sign-in sessions, each with the IP address and browser User-Agent you signed in from, kept by haruhime.moe (packs only reads the session)</td>
      <td>Keeping you signed in</td>
      <td>Contract</td>
      <td>Up to haruhime.moe; signing out on packs ends the session</td>
    </tr>
    <tr>
      <td>Packs you save, with their name, description, maps, visibility, and magnet links</td>
      <td>The service you asked for</td>
      <td>Contract</td>
      <td>You delete the pack or your packs data</td>
    </tr>
    <tr>
      <td>Every saved version of a pack, with who saved it and when</td>
      <td>Letting you see and keep a pack's history</td>
      <td>Contract</td>
      <td>You delete the pack or your packs data</td>
    </tr>
    <tr>
      <td>Your API key, if you create one. We store only a hash of it, its first 12 characters, and when it was created and last used.</td>
      <td>Letting your scripts and bots use the packs API</td>
      <td>Contract</td>
      <td>You revoke the key or delete your packs data</td>
    </tr>
    <tr>
      <td>Rate-limit counters, if you save or change packs, use the API, or create a key, keyed by your account, or by your IP address when a request to the API has no valid key or a request to our pools service has no valid token</td>
      <td>Protecting the service from abuse</td>
      <td>Our legitimate interest in preventing abuse</td>
      <td>About 2 minutes, or about an hour for the key-creation counter</td>
    </tr>
    <tr>
      <td>A counter of packs data deletions, keyed by your osu! user ID</td>
      <td>Protecting the service from abuse</td>
      <td>Our legitimate interest in preventing abuse</td>
      <td>About an hour</td>
    </tr>
    <tr>
      <td>Rate-limit counters for osu! lookups (star ratings, maps the mirror doesn't have, and the lookups for a pack's stats when you save it), keyed by your IP address, signed in or not</td>
      <td>Keeping one visitor from using up the site's osu! API quota</td>
      <td>Our legitimate interest in preventing abuse</td>
      <td>About 2 minutes</td>
    </tr>
    <tr>
      <td>Request logs kept by our host, Vercel, such as IP address and browser type</td>
      <td>Hosting and security</td>
      <td>Our legitimate interest in running the site securely</td>
      <td>As long as Vercel keeps them under its own policy</td>
    </tr>
  </tbody>
</table>

Every IP-keyed counter groups an IPv6 address by its /64 network. Our server never forwards your IP address to osu!.

## Your rights under the GDPR

- **Access and portability.** Get a copy of your data in a machine-readable file.
- **Rectification.** Have wrong data corrected.
- **Erasure.** Have your data deleted.
- **Restriction.** Ask us to pause using your data while a question about it is sorted out.
- **Objection.** Object to anything we do on the basis of legitimate interest.
- **Complaint.** Complain to your data protection supervisory authority.

## Your rights under the CCPA

- **Know, delete and correct.** Ask what we hold about you, ask us to delete it, and ask us to correct it.
- **Selling and sharing.** We don't sell or share personal information.
- **Opting out.** We honor Global Privacy Control signals.

To use any of these rights, email [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

### How to use your GDPR rights

- **Download my data** on your [packs settings page](https://packs.haruhime.moe/me) gives you a JSON file with everything packs holds about you: your profile details, your saved packs, and your API key's prefix and dates.
- **Delete my packs data** on the same page deletes your API key and every pack you saved. Your haruhime account (your sessions and the osu! link) is deleted on [haruhime.moe/account](https://www.haruhime.moe/account).
- Your name and avatar come from your osu! profile. Change them on osu!, and haruhime.moe picks up the new ones the next time you sign in.
- For anything else, email [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe). We answer within 30 days.

### More about the CCPA

- **What we collect.** Identifiers (your osu! user ID, username, and IP address) and internet activity (request logs).
- **Sensitive personal information.** We don't collect any.
- **Response time.** We answer within 45 days.

packs is small and free, and it may fall below the thresholds where the CCPA applies. We give these rights to everyone anyway, wherever you live.

## Changes

We may update this page. It was last updated on 2026-10-06.

## Contact

haruhime.moe runs packs.haruhime.moe. For anything on this page, write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

---

# Copyright & Takedown

Source: https://packs.haruhime.moe/legal/copyright

## We do not host files

packs.haruhime.moe does not host, store, or distribute beatmaps or any other files. Downloads come directly from third-party mirrors to the user's browser, and exports are created on the user's device and stored in places the user controls (their computer, or a torrent they seed).

That means we can't remove files from:

- **Torrents.** We don't run a tracker and can't remove a torrent from the swarm.
- **Beatmap mirrors.** The service downloads from mirror.hinamizawa.ai, which publishes its own DMCA takedown process at [https://mirror.hinamizawa.ai/docs/content-takedowns](https://mirror.hinamizawa.ai/docs/content-takedowns). Send notices there.
- **osu!** Beatmaps listed on osu! can be reported to ppy Pty Ltd.

## What we can remove

We store saved pack records: a pack name and description, beatmap IDs, and links the user recorded. If you believe a saved pack record infringes your rights, send a notice and we'll remove the record.

## Copyright and DMCA

packs doesn't host files. What you can save is pack metadata, a name, description, beatmap IDs and links, never a beatmap file.

Our designated agent for copyright notices is [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

### A takedown notice should include

1. your contact information;
2. the copyrighted work you believe is infringed;
3. the URL or page the material appears on;
4. a good-faith statement that the use is not authorized;
5. a sworn statement that you're the rights holder, or authorized to act for them;
6. your physical or electronic signature.

### A counter-notice should include

1. your contact information;
2. the material removed and where it appeared;
3. a sworn, good-faith statement that it was removed by mistake or misidentification;
4. your consent to the jurisdiction of your local courts, or haruhime.moe's;
5. your physical or electronic signature.

## Sources

Packs owned by haruhime pools are osu! tournament mappools published from [pools.haruhime.moe](https://pools.haruhime.moe), which credits each pool's sources on the pool's page. A pool there can come from the tournament's hosts, from a community submission, or from another source such as [otdb](https://otdb.sheppsu.me) by Sheppsu.

## Contact

haruhime.moe runs packs.haruhime.moe. For anything on this page, write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).

---

# Disclaimers

Source: https://packs.haruhime.moe/legal/disclaimers

packs.haruhime.moe ("packs") is an independent fan project. This page says who we're not, how our requests identify themselves, how torrents work here, and what our numbers mean.

## Not affiliated

packs is not affiliated with, endorsed by, or run by:

- ppy Pty Ltd or osu!;
- the osu! Tournament Committee;
- the hinamizawa beatmap mirror ([mirror.hinamizawa.ai](https://mirror.hinamizawa.ai)).

Not affiliated with or endorsed by ppy Pty Ltd. osu! is a trademark of ppy Pty Ltd.

## How our requests identify themselves

- **Our server to the osu! API.** Our server calls the osu! API for maps the mirror doesn't have, for star ratings with mods, and to work out a saved pack's stats. Those requests send `User-Agent: packs.haruhime.moe (+https://packs.haruhime.moe; haruhime@haruhime.moe)`.
- **Signing in.** Sign-in with osu! runs on haruhime.moe, not on packs. Signing out on packs makes one request from our server to haruhime.moe, carrying only your session cookie.
- **Your browser to the mirror.** Beatmap files and beatmap data go from the mirror to your browser. Browsers don't let a website set the User-Agent, so these requests carry your browser's own User-Agent, plus the `Origin` and `Referer` headers that name packs.haruhime.moe.
- **Our server to the mirror.** To work out a saved pack's stats, and to list the maps on a saved pack's page, our server asks the mirror for the maps' details (never the beatmap files), with the same User-Agent.

## Made with AI help

packs was built with help from AI coding tools (Claude, by Anthropic). A person reviews, tests, and ships every change.

## Estimates, not rulings

- **Star ratings with mods.** Where packs shows a star rating with mods, it comes from the osu! API and is kept for up to 30 days, so it can lag behind a difficulty update on osu!.
- **Pack stats.** The star rating, length and BPM ranges on public packs are worked out when a pack is saved. They can lag behind a map update on osu!, and while a lookup fails they cover only the maps we could look up.

## Torrents

Torrents are peer-to-peer: the files come from other people's computers, not from us. packs doesn't host, seed, or track any files. The magnet links on packs are added by pack owners, and we can't check what their torrents contain. You're responsible for what you download and share, and for following the law where you live. If torrenting, or downloading these maps, isn't legal for you, don't do it.

## Beatmaps belong to their creators

Beatmaps, and the music, art, and video in them, belong to their mappers, artists, and other rights holders. Maps come from the mirror to your browser, and we don't host them. To report a problem, see [Copyright & Takedown](https://packs.haruhime.moe/legal/copyright).

## Contact

haruhime.moe runs packs.haruhime.moe. For anything on this page, write to [haruhime@haruhime.moe](mailto:haruhime@haruhime.moe).
