# Manufacturers

Retrieve information about firearms manufacturers, their history, headquarters, and full product catalogs.

Source: https://docs.gunspec.io/en/api/manufacturers

## List Manufacturers

`GET /v1/manufacturers`

Auth: API key required  
Tier: Explorer+

Returns a paginated list of firearm manufacturers.

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Page number, from 1 to 10,000 |
| `per_page` | integer | no | Items per page (max 100) |
| `country` | string | no | Filter by ISO 3166-1 alpha-2 country code |
| `search` | string | no | Substring match on manufacturer name |
| `If-None-Match` | string | no | The `ETag` from a previous response. If the body would be byte-identical, the API answers `304 Not Modified` with no body. A 304 counts toward the per-minute rate limit but not the daily request allowance. |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | Manufacturer[] | One page of results |
| `data[].id` | string | URL-safe slug identifying the manufacturer, e.g. `glock`. |
| `data[].name` | string | Display name, as the maker writes it. |
| `data[].countryCode` | string \| null | ISO 3166-1 alpha-2 code of where the company is based. |
| `data[].foundedYear` | integer \| null | Year the company was founded. |
| `data[].status` | string \| null | Whether the company still trades. Much of this catalog is historical, and null means nobody has established which. |
| `data[].defunctYear` | integer \| null | Year the company ceased trading, where that is established. Null while it still trades, and also null where the closing year is simply unknown, so an absent value is never read as "still trading". |
| `data[].stateOwned` | boolean \| null | Government owned. **Null is not false**: it means nobody has established it, and a consumer treating null as false would report every unchecked company as private. |
| `data[].makesFirearms` | boolean \| null | Builds the products this catalog lists, as against importing, rebranding or customising them. Scope is the catalog rather than a legal definition: air rifles are listed here as rifles and pistols, so their makers are true. Null means unestablished. |
| `data[].parentId` | string \| null | The manufacturer that owns this one today. |
| `data[].predecessorId` | string \| null | The manufacturer this one succeeded, which is not the same as being owned by it: U.S. Repeating Arms followed Winchester without Winchester owning it. |
| `data[].website` | string \| null | Official website, where one is known. |
| `data[].logoUrl` | string \| null | Absolute URL of the logo, or null where none is on file. |
| `data[].description` | string \| null | Prose summary of the record, where one has been written. |
| `data[].createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data[].updatedAt` | string | When a field we serve last changed. Maintained by the database rather than by whatever wrote the row, so it moves for a corrected specification and does not move for a re-run of the importer. |
| `data[].version` | string \| null | Opaque fingerprint of this record. It changes when and only when a field we serve changes, so a client holding the same value already has the record and need not fetch it. Equality is the only supported operation: do not parse it, and do not assume how it is computed. Null means the record has not been written since versioning began, not that it is unchanged. |
| `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",
      "name": "Glock Ges.m.b.H.",
      "countryCode": "AT",
      "foundedYear": 1963,
      "status": "active",
      "defunctYear": null,
      "stateOwned": false,
      "makesFirearms": true,
      "parentId": null,
      "predecessorId": null,
      "website": "https://www.glock.com",
      "logoUrl": null,
      "description": null,
      "createdAt": "2025-01-15 12:00:00",
      "updatedAt": "2026-09-10 06:42:19",
      "version": "a3f1c2d4e5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "per_page": 20,
    "total": 939,
    "totalPages": 47
  }
}
```

### 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/manufacturers?country=AT' \
  --header 'X-API-Key: your_key'
```

## Get Manufacturer

`GET /v1/manufacturers/{id}`

Auth: API key required  
Tier: Explorer+

Returns a single manufacturer with its founding details and website.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Manufacturer slug |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `If-None-Match` | string | no | The `ETag` from a previous response. If the body would be byte-identical, the API answers `304 Not Modified` with no body. A 304 counts toward the per-minute rate limit but not the daily request allowance. |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | Manufacturer | The response payload |
| `data.id` | string | URL-safe slug identifying the manufacturer, e.g. `glock`. |
| `data.name` | string | Display name, as the maker writes it. |
| `data.countryCode` | string \| null | ISO 3166-1 alpha-2 code of where the company is based. |
| `data.foundedYear` | integer \| null | Year the company was founded. |
| `data.status` | string \| null | Whether the company still trades. Much of this catalog is historical, and null means nobody has established which. |
| `data.defunctYear` | integer \| null | Year the company ceased trading, where that is established. Null while it still trades, and also null where the closing year is simply unknown, so an absent value is never read as "still trading". |
| `data.stateOwned` | boolean \| null | Government owned. **Null is not false**: it means nobody has established it, and a consumer treating null as false would report every unchecked company as private. |
| `data.makesFirearms` | boolean \| null | Builds the products this catalog lists, as against importing, rebranding or customising them. Scope is the catalog rather than a legal definition: air rifles are listed here as rifles and pistols, so their makers are true. Null means unestablished. |
| `data.parentId` | string \| null | The manufacturer that owns this one today. |
| `data.predecessorId` | string \| null | The manufacturer this one succeeded, which is not the same as being owned by it: U.S. Repeating Arms followed Winchester without Winchester owning it. |
| `data.website` | string \| null | Official website, where one is known. |
| `data.logoUrl` | string \| null | Absolute URL of the logo, or null where none is on file. |
| `data.description` | string \| null | Prose summary of the record, where one has been written. |
| `data.createdAt` | string | When the record was first added, `YYYY-MM-DD HH:MM:SS` in UTC. |
| `data.updatedAt` | string | When a field we serve last changed. Maintained by the database rather than by whatever wrote the row, so it moves for a corrected specification and does not move for a re-run of the importer. |
| `data.version` | string \| null | Opaque fingerprint of this record. It changes when and only when a field we serve changes, so a client holding the same value already has the record and need not fetch it. Equality is the only supported operation: do not parse it, and do not assume how it is computed. Null means the record has not been written since versioning began, not that it is unchanged. |

```json
{
  "success": true,
  "data": {
    "id": "glock",
    "name": "Glock Ges.m.b.H.",
    "countryCode": "AT",
    "foundedYear": 1963,
    "status": "active",
    "defunctYear": null,
    "stateOwned": false,
    "makesFirearms": true,
    "parentId": null,
    "predecessorId": null,
    "website": "https://www.glock.com",
    "logoUrl": null,
    "description": null,
    "createdAt": "2025-01-15 12:00:00",
    "updatedAt": "2026-09-10 06:42:19",
    "version": "a3f1c2d4e5b6a7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2"
  }
}
```

### 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 GET \
  --url 'https://api.gunspec.io/v1/manufacturers/glock' \
  --header 'X-API-Key: your_key'
```

## List Firearms by Manufacturer

`GET /v1/manufacturers/{id}/firearms`

Auth: API key required  
Tier: Explorer+

Returns the firearms produced by a manufacturer.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Manufacturer slug |

### Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | integer | no | Page number, from 1 to 10,000 |
| `per_page` | integer | no | Items per page (max 100) |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | ManufacturerFirearmListItem[] | One page of results |
| `data[].id` | string | URL-safe slug identifying the firearm, e.g. `glock-g17-gen5`. |
| `data[].name` | string | Display name, as the maker writes it. |
| `data[].categoryId` | string | Slug of the category it belongs to. Resolve against `/v1/categories`. |
| `data[].yearIntroduced` | integer \| null | Year it entered production or service. Null where the date is disputed or unknown. |
| `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[].actionType` | string \| null | How the action cycles: `short_recoil`, `gas_operated`, `blowback`, `bolt_action` and so on. An open vocabulary: the values in use are published on the schema as `x-gunspec-vocabulary`, and `GET /v1/firearms/action-types` is the live list. |
| `data[].countryOfOrigin` | string \| null | ISO 3166-1 alpha-2 code of where it was designed, e.g. `AT`. |
| `data[].svgLineArtUrl` | string \| null | Line-art silhouette, or null where none has been drawn. |
| `data[].model3dUrl` | string \| null | GLB model, or null where none is on file. |
| `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",
      "name": "Glock 17 Gen5",
      "categoryId": "pistol",
      "yearIntroduced": 2017,
      "status": "in_production",
      "actionType": "short_recoil",
      "countryOfOrigin": "AT",
      "svgLineArtUrl": null,
      "model3dUrl": null,
      "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": 939,
    "totalPages": 47
  }
}
```

### 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 GET \
  --url 'https://api.gunspec.io/v1/manufacturers/glock/firearms' \
  --header 'X-API-Key: your_key'
```

## Manufacturer Timeline

`GET /v1/manufacturers/{id}/timeline`

Auth: API key required  
Tier: Studio+

Returns the manufacturer catalog ordered by year of introduction, for plotting output over time.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Manufacturer slug |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.manufacturer` | object | Who the timeline is for. |
| `data.manufacturer.id` | string | Slug of the manufacturer. |
| `data.manufacturer.name` | string | Display name, as the maker writes it. |
| `data.timeline` | object[] | One entry per year they introduced something, oldest first. |
| `data.timeline[].year` | integer \| null | Year of introduction. Null groups the firearms whose year is unrecorded. |
| `data.timeline[].firearms` | object[] | The firearms they introduced that year. |

```json
{
  "success": true,
  "data": {
    "manufacturer": {
      "id": "glock",
      "name": "Glock Ges.m.b.H."
    },
    "timeline": [
      {
        "year": 1982,
        "firearms": [
          {
            "id": "glock-g17-gen5",
            "name": "Glock 17 Gen5",
            "yearIntroduced": 1982,
            "categoryId": "pistol",
            "status": "in_production"
          }
        ]
      }
    ]
  }
}
```

### 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 |

### Notes

- Requires **Studio** tier or higher.
- Timeline entries are sorted chronologically by year.
- Firearms without a known `year_introduced` are omitted from the timeline.

### Example

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

## Manufacturer Stats

`GET /v1/manufacturers/{id}/stats`

Auth: API key required  
Tier: Builder+

Returns aggregate figures for one manufacturer: how many firearms, which categories and calibers they span, and the range of years covered.

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | yes | Manufacturer slug |

### Response 200 (application/json)

| Field | Type | Description |
| --- | --- | --- |
| `data` | object | The response payload. |
| `data.manufacturer` | object | Who the figures are about. |
| `data.manufacturer.id` | string | Slug of the manufacturer. |
| `data.manufacturer.name` | string | Display name, as the maker writes it. |
| `data.stats` | object | The aggregate figures. snake_case, as the rollup query names them. |
| `data.stats.total_firearms` | integer | How many catalogued firearms they make. |
| `data.stats.avg_weight_g` | number \| null | Mean unloaded weight in grams across their catalog, over the firearms that record one. |
| `data.stats.avg_range_m` | number \| null | Mean effective range in metres, over the firearms that record one. |
| `data.stats.avg_capacity` | number \| null | Mean magazine capacity, over the firearms that record one. |
| `data.stats.active_count` | integer | How many of their firearms are still in production. |
| `data.stats.discontinued_count` | integer | How many of their firearms are discontinued. |
| `data.stats.earliest_year` | integer \| null | Introduction year of their earliest catalogued firearm. Null when none records one. |
| `data.stats.latest_year` | integer \| null | Introduction year of their most recent catalogued firearm. Null when none records one. |
| `data.categories` | object[] | Categories they build in, most first. |
| `data.categories[].id` | string | Slug of the category. |
| `data.categories[].name` | string | Display name, as the maker writes it. |
| `data.categories[].count` | integer | How many of their firearms are in it. |
| `data.mostCommonCaliber` | object | The single cartridge most of their catalog chambers. Null where none of their firearms records a caliber. |
| `data.mostCommonCaliber.id` | string | Slug of the cartridge. |
| `data.mostCommonCaliber.name` | string | Display name, as the maker writes it. |
| `data.mostCommonCaliber.count` | integer | How many of their firearms chamber it. |

```json
{
  "success": true,
  "data": {
    "manufacturer": {
      "id": "glock",
      "name": "Glock Ges.m.b.H."
    },
    "stats": {
      "total_firearms": 134,
      "avg_weight_g": 669.59,
      "avg_range_m": 48.32,
      "avg_capacity": 13.31,
      "active_count": 97,
      "discontinued_count": 37,
      "earliest_year": 1982,
      "latest_year": 2026
    },
    "categories": [
      {
        "id": "pistol",
        "name": "Pistol",
        "count": 134
      }
    ],
    "mostCommonCaliber": {
      "id": "9x19mm-parabellum",
      "name": "9x19mm Parabellum",
      "count": 63
    }
  }
}
```

### 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 |

### Notes

- Requires **Builder** tier or higher.
- Average values only include firearms where the relevant field is populated.
- Active/discontinued status is derived from the `status` field on each firearm.

### Example

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