Security
How access to the API is decided, how a credential can fail and what that tells you, what we do when something breaks, and how to report a weakness. The mechanics behind each request live on How we protect your data.
- 118/135operations need a credential
- 2ways to send a key
- 39reasons a request can be refused
- TLSHSTS with preload
Principles
Four rules, each with the code that enforces it.
Least privilege
A key sees what its plan allows and nothing more. Fields are shaped per plan after every handler; endpoints are gated by tier in one middleware.
Field shapingDefence in depth
TLS, hardening headers, an origin allow-list, body limits, per-minute and daily limiters and pagination caps stack; a request that gets past one still meets the next.
The request pipelineData minimisation
An account is an email and a plan. Card details stay with Stripe, error logs hold a code and a request id, staff audit rows never store a body.
Privacy and complianceSecure by default
There is one configuration and it is the safe one: nothing for you to switch on, and the only opt-in on our side is analytics behind a consent banner.
Headers on every responseHow a caller is identified
Four kinds of credential reach the API. Each is verified differently and fails with its own reasons, so the error tells you which one is at fault.
| Credential | Verified by | Fails as |
|---|---|---|
| API key | Looked up in the key store on every request, with its plan and owner. Disabled and expired keys are refused before any handler runs. Sent as X-API-Key or Authorization. | |
| Website session | An HttpOnly, Secure cookie on our domain, backed by a database row and cached; a cache miss rebuilds it from the row rather than signing you out. Separate from API keys and from the staff console's own session. | |
| The website itself | A server-side proxy token the browser never sees. Grants no plan and no key: the site renders with the full field policy but cannot be borrowed to call the API. | |
| Marketplace proxy | A proxy secret plus the subscription header the marketplace forwards. The plan name maps to a tier; the marketplace meters the quota. |
401 or 403: which one you got matters
A 401 is a credential problem: missing, malformed, disabled or expired, and presenting a different one can work. A 403 means the credential is valid and the caller is not permitted; rotating a key changes nothing. Every response names the exact reason.
401401: the credential
Key hygiene
What keeps a key a secret.
Do
- Send a key only over TLS, in the
X-API-Keyheader or as a Bearer token, from server-side code. - Use one key per application or environment, so revoking one does not take down the others.
- Rotate keys on a schedule and revoke immediately when someone leaves; revocation is instant.
- Read
error.reasonbefore rotating: a403is never fixed by a new key. - Keep the key out of the URL: query strings land in logs and referrer headers.
Do not
- Ship a key in browser or mobile code, a public repository, or a shared screenshot.
- Reuse one key across several products, or share it between people.
- Send the same key from many unrelated clients: when 3 or more keys share one client fingerprint the account is flagged, and one key spread across many looks the same.
- Rely on the playground's stored key for production: it lives in that browser only and is never sent to us for storage.
When something goes wrong on our side
What is in place before an incident, and what you will see during one.
Status page
Live and historic availability at status.gunspec.io. The footer of every docs page shows the current state.
Kill switch, maintenance, read-only
Operators can pause or freeze the public API without touching their own console. You see a 503 with Retry-After and a named reason.
Error alarm
An hourly check compares 5xx rates and pages operators on a spike. A deliberate 5xx is logged like an outage, so nothing we raise on purpose is invisible to us.
Request ids
Every response carries X-Request-Id and every error echoes it. Quote it; it finds the request, its status and its error code in our logs.
Staff audit trail
Every mutating staff action is written to an append-only log with actor, target and origin. Bodies are never stored.
Sealed secrets and backups
Operator credentials are encrypted with authenticated encryption before they reach the database, so a copied backup is not a compromised account.
Reporting a vulnerability
We want to hear about it, privately and first. The full policy is on the main site; this is the short form.
- 1Email security@gunspec.io with a clear description, the steps to reproduce, the affected URL or endpoint, and any proof of concept.
- 2Include an
X-Request-Idif you have one; it takes us straight to the request. - 3One issue per message where practical. We acknowledge, keep you updated, and credit you with your permission.
In scope
- gunspec.io, api.gunspec.io and the systems we operate to deliver them.
Out of scope
- Denial of service, volumetric or spam testing.
- Social engineering of staff or users, and physical attacks.
- Reports from automated tools without a demonstrated, exploitable impact.
- Our hosting and payment providers, which run their own programmes.
Safe harbour
Good-faith research that follows the policy is authorised. We will not pursue legal action for it, and if a third party does, we will make that authorisation known. Use test accounts and test data, do not touch data that is not yours, and give us reasonable time to fix before disclosing.
Recognition
No paid bounty programme today. Valid reports are acknowledged publicly, with your permission.
security@gunspec.ioRead the full Vulnerability Disclosure PolicyContact us