GunSpec
Recipe

Render the artwork

Silhouettes, cartridge drawings, photographs and 3D models, and the one decision that matters: a URL, raw bytes, or a data URI.

Every asset can be had three ways, and picking wrongly is what makes a page heavy or a feed unreadable. The media page lists the kinds; this is which representation to ask for.

Where it is goingAsk forWhy
An <img> on a web pageurl, or sizes.displayThe browser caches it, the CDN serves it, and your HTML stays small.
Markup you want to styleformat=jsonThe SVG source, inlined into your own DOM, so CSS can fill and colour it.
An email, a PDF, a feed rowformat=datauriNothing else can make a second request. The bytes travel with the document.
A file on disk or in your own bucketthe SDK raw helpersBytes plus content type, with the CDN redirect already followed.
A 3D viewer/modelA GLB, served as model/gltf-binary. Load it once and cache it hard; it is the largest thing we serve.

Most of the catalog has a drawing, which is why a silhouette is the right fallback when a photograph does not exist. format decides what you get back, and stroke_width with stroke_color injects an outline so the shape reads on any background.

silhouette
TypeScript
Inline the source when the drawing has to inherit your theme:
1/2
import { GunSpec } from '@buun_group/gunspec-sdk' const gunspec = new GunSpec({ apiKey: process.env.GUNSPEC_API_KEY }) // an inlined SVG can be filled by CSS, an <img> cannot.const { data } = await gunspec.firearms.getSilhouette('glock-g17', { format: 'json' })const markup = data.svg
  • The drawings are filled shapes, so on a dark surface they disappear. Inject a stroke, or invert them in CSS when you inline the source.
  • Inline the SVG when the drawing must match your theme. An &lt;img&gt; cannot be recoloured; inlined markup can.
  • They scale to any size with no derivative, which is what makes them the cheap choice for list rows and comparison tables.

A data URI is base64, so it is about a third larger than the file and it cannot be cached separately from the document that carries it. That is a fair trade in exactly three places.

SituationVerdict
HTML email, or a PDF you generateYes. The client will not fetch a remote image, and often will not be online.
A product feed or spreadsheet export a partner ingestsYes, for small drawings. It removes a fetch step from their importer.
A list page in a browserNo. Forty rows of base64 is a megabyte of HTML that caches with the page and dies with it.
An avatar-sized icon repeated across a siteNo. One cached file beats the same bytes inlined on every page.
A canvas or PDF renderer that cannot do async fetchesYes. That is the case the format exists for.

Every catalogued load draws its own cartridge at /bullet.svg, and the drawings are to scale against each other, so a row of them is a size comparison rather than decoration. Pair them with the numbers from cartridges and ballistics.

bullet.svg
TypeScript
Returns the SVG as a string.
1/3
const svg = await gunspec.ammunition.getBulletSvg('9x19mm-124gr-fmj')

A hosted asset carries sizes with a thumb, a display and a full version, plus width, height, alt where one is written, and credit where the catalogue records it. Serve the derivative you display and render the credit, which the licence page explains is a condition rather than a courtesy.

hero-image.ts
javascript
// Photographs and renders come with their derivatives, so the// browser downloads the size it is going to display.const { data: media } = await gunspec.firearms.listMedia('glock-g17', { kind: 'photo' }) const hero = media[0]const img = {  src: hero.sizes?.display ?? hero.url,  srcSet: hero.sizes    ? `${hero.sizes.thumb} 320w, ${hero.sizes.display} 1024w, ${hero.sizes.full} 2048w`    : undefined,  // Null, not an empty string, where nobody has written alt text.  alt: hero.alt ?? `${firearm.name}`,  width: hero.width,  height: hero.height,} // The credit is a condition of use, not a caption you may drop.if (hero.credit) caption.textContent = hero.credit

Records that carry one serve a GLB at /model, as model/gltf-binary. It is the largest thing this API returns, so fetch it once, store it, and serve it from your own origin. The SDK raw helpers hand you the bytes with the content type already resolved.

  • Filter a list with has_3d_model=true when a feature only works for records that have one.
  • Load it lazily. A viewer that downloads a model before the reader has asked to see it is the heaviest page on your site.
  • The same applies to schematics and large photographs: fetch once into your own storage rather than proxying every view.

Four habits that keep an image-heavy page fast and keep your quota for data rather than pictures.

  • Assets are served from the CDN and are safe to cache hard. It is the metadata call that has a lifetime; see caching.
  • Copy what you use into your own bucket if you need guaranteed availability and your own cache headers. The licence allows serving them in your product.
  • Set width and height from the metadata so the page does not reflow when the image lands.
  • Alt text is null where nobody has written it. Fall back to the record name; never render an empty string.