---
title: "Compare the free recoil of each firearm"
method: GET
path: "/v1/firearms/recoil"
tags: ["Firearm Analysis"]
---

# Compare the free recoil of each firearm

`GET /v1/firearms/recoil`

## Request

- Base URL: `https://api.gunspec.io`
- URL: `https://api.gunspec.io/v1/firearms/recoil`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer

Returns the free recoil of each of up to 5 firearms firing a load: the velocity the firearm is pushed back at, the energy it takes and the impulse, by the free recoil formula the Sporting Arms and Ammunition Manufacturers' Institute (SAAMI) publishes.

The firearm's mass is its recorded loaded weight (`mass=empty` for its empty weight; the empty weight is used, with a warning, where no loaded weight is recorded). The bullet is the load's record, and its muzzle velocity is the one computed for this firearm's own barrel, as `/v1/firearms/{id}/calculate` computes it. With no `ammo_id`, each firearm fires the load `/v1/firearms/{id}/load` fires it with.

The catalogue records no powder charge. Without `powder_charge_g`, only the bullet's momentum is counted, and since the powder gas leaves the muzzle the same way, the real free recoil is larger: every figure is then a lower bound, and `recoil.lowerBound` says so. With a charge, the gas is added at SAAMI's gas velocity factor for the class of firearm (1.75 times the muzzle velocity for high powered rifles, 1.50 for handguns and average shotguns, 1.25 for long-barrelled shotguns). A submachine gun or personal defence weapon is in none of SAAMI's classes: name one with `gas_class` to count its gas.

Free recoil is the reaction of a firearm with nothing holding it. It is not felt recoil, and SAAMI's formula does not count a muzzle brake or suppressor redirecting the gas.

**Requires Builder tier or higher.**

## Query parameters

- `ids` string, required
- `ammo_id` string
- `mass` 'loaded' | 'empty'
- `powder_charge_g` number
- `gas_class` 'rifle' | 'shotgun' | 'shotgun_long_barrel' | 'handgun'

## Headers

- `If-None-Match` string

## Response `200`

Compare the free recoil of each firearm

- object
  - `success` boolean, required: Always true on a successful response. Every error carries `false` and an `error` object instead.
  - `data` object, required: The response payload.
    - `method` object: The formula behind every figure in the answer.
      - `id` string: The method the figures were computed with.
      - `name` string: The method by name.
      - `equation` string: The equation as written, with its units, so the figures can be reproduced.
      - `citations` object[]: Where the formula and its factors are from.
        - `authors` string: Authors or the issuing body.
        - `year` integer: Year of publication or revision.
        - `title` string: Title.
        - `journal` string: Where it is published: journal, publisher or document number.
        - `doi` string, nullable: Digital Object Identifier, where it has one.
        - `url` string, nullable: A page it can be read at, where it has no DOI.
      - `gasFactors` object: SAAMI's gas velocity factor for each class, as a multiple of the muzzle velocity.
        - `rifle` number: Gas velocity factor for high powered rifles.
        - `shotgun` number: Gas velocity factor for shotguns (average length).
        - `shotgun_long_barrel` number: Gas velocity factor for shotguns (long barrel).
        - `handgun` number: Gas velocity factor for pistol & revolvers.
    - `shot` object: The shot every firearm was answered for.
      - `mass` 'loaded' | 'empty': Which recorded weight was asked for.
      - `powderChargeG` number, nullable: The powder charge given, in grams. Null when none was.
      - `gasClass` 'rifle' | 'shotgun' | 'shotgun_long_barrel' | 'handgun' | 'null', nullable: The class given in the query. Null when each firearm's class came from its category.
      - `lowerBound` boolean: True when no powder charge was given, so every figure is a lower bound.
      - `defaulted` string[]: The query parameters not sent, answered from their defaults.
    - `results` object[]: One result per firearm, in the order their ids were given.
      - `firearm` object: The firearm this result is for.
        - `id` string: Firearm slug.
        - `name` string: Firearm name.
        - `categoryId` string: Category slug.
        - `barrelLengthMm` number, nullable: Barrel length in mm, from the record.
      - `ammunition` object, nullable: The load fired: the one named by `ammo_id`, or the one `/v1/firearms/{id}/load` fires this firearm with. Null when there is none.
        - `id` string: Ammunition slug.
        - `name` string: Load name.
        - `caliberId` string: Cartridge slug.
        - `bulletWeightG` number: Bullet mass in grams, from the load record.
      - `mass` object: The mass that recoils.
        - `firearmG` number, nullable: The firearm's mass in grams.
        - `basis` 'loaded' | 'empty' | 'null', nullable: Which recorded weight it is.
        - `source` 'catalogue' | 'null', nullable: `catalogue` when the record states it.
      - `muzzleVelocityMps` number, nullable: The load's muzzle velocity from this firearm's barrel, in m/s.
      - `gas` object: The powder gas term.
        - `class` 'rifle' | 'shotgun' | 'shotgun_long_barrel' | 'handgun' | 'null', nullable: The SAAMI class applied. Null when none applies.
        - `label` string, nullable: The class as SAAMI names it.
        - `factor` number, nullable: Its gas velocity factor.
        - `velocityMps` number, nullable: The powder gas velocity counted, in m/s. Null when the gas was not counted.
        - `chargeG` number, nullable: The powder charge counted, in grams.
        - `classSource` 'query' | 'category' | 'null', nullable: Whether the class was named in the query or read from the category.
      - `recoil` object, nullable: The free recoil. Null when a figure it needs is not recorded; `error` says which.
        - `impulseNs` number: Momentum the firearm takes, in N·s.
        - `bulletImpulseNs` number: The bullet's share of it.
        - `gasImpulseNs` number, nullable: The powder gas's share. Null when it was not counted.
        - `velocityMps` number: Free recoil velocity in m/s.
        - `energyJ` number: Free recoil energy in joules.
        - `lowerBound` boolean: True when the gas was not counted, so the real recoil is larger.
      - `delta` object, nullable: Against the option with the least recoil energy. Null on that option, on a lone firearm, and where no recoil was computed.
        - `energyJ` number: Joules more than the softest-recoiling option.
        - `velocityMps` number: Metres per second more.
        - `impulseNs` number: N·s more.
      - `warnings` string[]: What an answer assumed in place of a missing figure, or why the gas was not counted.
      - `error` string, nullable: Why this firearm could not be answered. Null when it was.

## Other responses

- `304`: Your copy is current. No body is sent, and this response does not count toward the daily request allowance.
- `400`: Validation error
- `401`: Missing or invalid API key
- `403`: The credential is valid and the caller is not permitted. Three causes, none of which reissuing a key changes: the plan is below the endpoint's tier, the account is suspended, or the key is not attached to the thing being acted on, meaning a user account for `/v1/me` or a shop for `/v1/vendor`. Distinct from 401, which means the credential is missing, malformed or expired and a new one can work.
- `404`: Resource not found
- `429`: A limit was reached. `RATE_LIMITED` is the per-minute limit: wait `Retry-After` seconds (a minute) and retry. `DAILY_CAP_EXCEEDED` is the plan's daily limit, per key: nothing will be served until the counters return to zero at midnight UTC. `MONTHLY_CAP_EXCEEDED` is the plan's monthly allowance, for the whole account: nothing will be served until midnight UTC on the first of next month, or until the plan changes. For both, `Retry-After` is the seconds until then and `error.resetsAt` says when as an instant. Calls made after a refusal are refused too and do not spend the month, and a free key that keeps calling is paused, so stop at the first one and resume at the reset.
- `500`: Internal server error

---

[API](https://skmtc.dev/gunspec/apis/gunspec-io-api.md) · [All operations](https://skmtc.dev/gunspec/apis/gunspec-io-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gunspec/apis/gunspec-io-api/revisions/f2e974d8b05c?raw)
