# Favorites

The signed-in account's favourited firearms. Session-scoped: these call `/v1/me/*` and answer 401 without a key that belongs to a user account. A favourite is a slug, not a copy of the record, so a firearm renamed in the catalog stays favourited.

Source: https://docs.gunspec.io/en/sdk/favorites

## client.favorites.list()

`GET /v1/me/favorites`

Auth: API key required  
Tier: Explorer+

The full favourited records, paginated.

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Page number |
| `per_page` | integer | no | Items per page (max 100). Preferred over `limit`. |
| `limit` | integer | no | Legacy alias for `per_page`. `per_page` wins if both are sent. |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | FavoriteFirearm[] | One favorited firearm, enough to render a card without a second call. **The field names are snake_case here**, like the search results and unlike `/v1/firearms`. `firearm_id` and `id` carry the same slug. |
| `data[].id` | string | URL-safe slug identifying the firearm. The same value as `firearm_id`. |
| `data[].firearm_id` | string | Slug of the firearm, under the favorites table's own column name. The same value as `id`. |
| `data[].favorited_at` | string | When this account favorited it, as the database stamps it (`YYYY-MM-DD HH:MM:SS`, UTC). The list is ordered by this, newest first. |
| `data[].name` | string | Display name, as the maker writes it. |
| `data[].manufacturer_id` | string \| null | Slug of the manufacturer. `manufacturerId` on the camelCase endpoints. |
| `data[].manufacturer_name` | string \| null | Display name of the manufacturer, joined in so a card renders without a second call. |
| `data[].category_id` | string \| null | Slug of the category. `categoryId` on the camelCase endpoints. |
| `data[].category_name` | string \| null | Display name of the category. |
| `data[].status` | FirearmStatus \| null | Production status, one of `in_production`, `discontinued`, `out_of_production`, `in_service`, `limited_production`, `prototype`. Discontinued records are kept deliberately: a rifle built in 2004 wears parts nobody sells today. |
| `data[].year_introduced` | integer \| null | Year the model was introduced. |
| `data[].action_type` | string \| null | Operating action as stored, underscored, e.g. `short_recoil`, `bolt_action`, `gas_operated`. The full vocabulary is `/v1/firearms/action-types`; a hyphenated value matches nothing. |
| `data[].country_of_origin` | string \| null | ISO 3166-1 alpha-2 code of the country of origin. |
| `data[].weight_empty_g` | number \| null | Unloaded weight in grams, one of the two figures a catalogue card draws beside the year. Null where the record has none. |
| `data[].barrel_length_mm` | number \| null | Barrel length in millimetres, the other of the two figures a card draws. Null where the record has none. |
| `data[].svg_line_art_url` | string \| null | Line-art silhouette, or null where none has been drawn. |
| `data[].model_3d_url` | string \| null | GLB model, or null where none is on file. |
| `data[].favorite_count` | integer | How many accounts have favorited this record. |
| `data[].images` | InlineMediaItem[] | Every image this firearm has, silhouette first. Empty when none are on file. |
| `data[].images[].id` | integer | Autoincrementing image id. |
| `data[].images[].url` | string | Absolute, directly fetchable URL. |
| `data[].images[].kind` | MediaKind | What kind of asset a media row is. |
| `data[].images[].alt` | string \| null | Alt text, where one has been written. Null otherwise, so do not render an empty string. |
| `data[].images[].width` | integer \| null | Pixel width, where it is known. |
| `data[].images[].height` | integer \| null | Pixel height, where it is known. |
| `pagination.page` | number | Current page number |
| `pagination.per_page` | number | Items per page |
| `pagination.total` | number | Total matching records (Builder and above) |

```json
{
  "success": true,
  "data": [
    {
      "id": "glock-g17-gen5",
      "firearm_id": "glock-g17-gen5",
      "favorited_at": "2026-05-01 09:14:03",
      "name": "Glock 17 Gen5",
      "manufacturer_id": "glock",
      "manufacturer_name": "Glock Ges.m.b.H.",
      "category_id": "pistol",
      "category_name": "Pistol",
      "status": "in_production",
      "year_introduced": 2017,
      "action_type": "short_recoil",
      "country_of_origin": "AT",
      "weight_empty_g": 625,
      "barrel_length_mm": 114,
      "svg_line_art_url": null,
      "model_3d_url": null,
      "favorite_count": 12,
      "images": [
        {
          "id": 2572,
          "url": "https://api.gunspec.io/v1/firearms/ak-47/images/2572",
          "kind": "silhouette",
          "alt": "AK-47 right profile",
          "width": 1536,
          "height": 1024
        }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "per_page": 20,
    "total": 42,
    "totalPages": 3
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request GET \
  --url 'https://api.gunspec.io/v1/me/favorites' \
  --header 'X-API-Key: your_key'
```

## client.favorites.listIds()

`GET /v1/me/favorites/ids`

Auth: API key required  
Tier: Explorer+

List just the ids of your favorited firearms. Cheaper than list when you only need to render a filled/unfilled state across a grid.

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.ids` | string[] | Slugs of every firearm you have favorited, newest first. |

```json
{
  "success": true,
  "data": {
    "ids": [
      "glock-g17-gen5"
    ]
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request GET \
  --url 'https://api.gunspec.io/v1/me/favorites/ids' \
  --header 'X-API-Key: your_key'
```

## client.favorites.add()

`POST /v1/me/favorites/{firearmId}`

Auth: API key required  
Tier: Explorer+

Favourite a firearm. Idempotent: favouriting twice is one favourite.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `firearmId` | string | yes | Firearm slug |

### Response 201 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.favorited` | boolean | Always true: the firearm is in your favorites once this returns. |
| `data.firearmId` | string | Slug of the firearm this row belongs to. |

```json
{
  "success": true,
  "data": {
    "favorited": true,
    "firearmId": "glock-g17-gen5"
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request POST \
  --url 'https://api.gunspec.io/v1/me/favorites/glock-g17-gen5' \
  --header 'X-API-Key: your_key'
```

## client.favorites.remove()

`DELETE /v1/me/favorites/{firearmId}`

Auth: API key required  
Tier: Explorer+

Remove a favourite. Idempotent: removing one you do not hold is not an error.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `firearmId` | string | yes | Firearm slug |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.favorited` | boolean | Always false: the firearm is not in your favorites once this returns. |
| `data.firearmId` | string | Slug of the firearm this row belongs to. |

```json
{
  "success": true,
  "data": {
    "favorited": false,
    "firearmId": "glock-g17-gen5"
  }
}
```

### Errors

| Status | Code | Message |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Validation error |
| 401 | `UNAUTHORIZED` | API key required |
| 403 | `FORBIDDEN` | Valid key, not permitted |
| 404 | `NOT_FOUND` | Resource not found |
| 429 | `RATE_LIMITED` | Rate limit exceeded |
| 500 | `INTERNAL_ERROR` | Unexpected server error |

### Example

```bash
curl --request DELETE \
  --url 'https://api.gunspec.io/v1/me/favorites/glock-g17-gen5' \
  --header 'X-API-Key: your_key'
```
