GunSpec
Practice

Cartridges, loads and ballistics

Four endpoints answer four different questions that people ask as one. Picking the wrong one puts rifle velocities on a pistol page.

Velocity is a property of a load fired through a barrel, not of a cartridge and not of a gun. Each row below fixes a different part of that sentence.

The questionThe callWhat it fixes
What is this cartridge, and what is it derived from?GET /v1/calibers/{id}The cartridge record: dimensions, case shape, the lineage links.
Roughly how does this cartridge perform?GET /v1/calibers/ballistics?id=A profile from the cartridge’s typical load. A shape, not a load you can buy.
What loads exist in it?GET /v1/calibers/{id}/ammunitionThe factory loads catalogued in that cartridge.
How does this load perform, through this barrel?GET /v1/ammunition/{id}/ballisticsOne load, your distances, your barrel length.
How does this gun perform with this load?GET /v1/firearms/{id}/load?ammo_id=The firearm’s own barrel, sampled out to 1000 m with terminal indices.
Does that agree with what the catalog publishes?GET /v1/firearms/{id}/calculate?ammo_id=Muzzle figures only, compared against the record’s own published figures.

Three calls, and the third is the one that matters: pass the barrel you are modelling. Omit it and you get the load’s reference barrel, which is fine for a box-label figure and wrong for a specific gun. Parameters are on the ammunition reference.

cartridge-to-table.ts
javascript
// From a cartridge to a number on a page.// 1. The cartridge, and what else is in its family.const { data: caliber } = await gunspec.calibers.get('300-blackout') // 2. The factory loads actually catalogued in it.const { data: loads } = await gunspec.calibers.getAmmunition('300-blackout') // 3. One load, computed out to the distances you want to plot -//    and through a specific barrel, because velocity is a property//    of the pairing and not of the box.const { data: table } = await gunspec.ammunition.ballistics(loads[0].id, {  barrel_length_mm: 406,  distances: '0,100,200,300,400,500',})

Two endpoints, deliberately separate, because a trajectory and a muzzle comparison are different pages.

firearm-load.ts
javascript
// The same question asked of a firearm rather than a load.// `load` samples the trajectory out to 1000 m using the firearm's// own barrel: this is what a "chamber it and plot it" page wants.const { data: profile } = await gunspec.firearms.load('sig-sauer-mcx', { ammo_id: '300-blk-125gr-otm' }) // `calculate` returns muzzle figures only and compares them with// the figures the catalog records for that firearm. That is how// you show a computed number honestly beside a published one.const { data: muzzle } = await gunspec.firearms.calculate('sig-sauer-mcx', { ammo_id: '300-blk-125gr-otm' })
  • load is the plotting endpoint: each point carries velocity, energy, drop, time of flight and the indices, so a chart needs one request.
  • calculate is the honesty endpoint: it returns the computed muzzle figures beside the ones the catalog records, which is what lets you show a difference rather than hide one.
  • Both need the cartridge to make sense for the firearm. Resolve the firearm first, read its calibers, and offer loads from those.

/v1/calibers/{id}/parent-chain walks upward, taking .300 Blackout back to .223 Remington, and /family returns the cartridge with its parent and everything derived from it. Use them for "related cartridges" rails and for explaining why two names describe nearly the same round. /v1/calibers/compare puts up to five side by side. The calibers reference has the fields.

A computed trajectory is a model, and the catalog’s published figures are somebody else’s measurement. The ballistics fields say which is which, and the accuracy note says what we do and do not warrant.

  • Label computed figures as computed, and say which barrel length and load they assume.
  • Where the record carries sourceMuzzleVelocityMps and the like, those are published figures with a source. Show them as the published ones.
  • Do not average a computed figure with a published one. They are different claims, and the mean of the two is a third number nobody stands behind.