Skip to content

Help — precision shooting

Precision shooting API and integration

← All guides

The precision shooting public API lets your club system create Matches, register athletes and read the result with nobody retyping anything.

The key

It is the same key as the other modules. The club owner creates it in Entity → API keys; the secret shows only once, when it is created. Keep it as carefully as a password.

Authorization: Bearer <access_key>.<secret>
  • The "read" scope allows GET only; "read and write" opens up the rest.
  • Everything is scoped to your club. There is no request that reaches another entity's data.
  • If the module is not enabled for the club, the answer is 404 — the key is valid, it is the resource that does not exist for you.

The three axes of a Match

Understanding this before you integrate saves most of the rework:

  • Modality — the course of fire. In the club catalog it is reusable; inside the Match it is a COPY. Editing the catalog afterwards does not change a Match already built — that is what keeps the result of a closed Match from ever changing. It is what the registration points to, and what the ranking groups by.
  • Series — the time axis: the round.
  • Relay — the space axis: who shoots together, with a slot limit.
One registration per modality. The same athlete can have two registrations in the same Match. Repeating the same modality is what returns a conflict.

A Match day in four calls

BASE=https://seu-dominio/api/v1
AUTH="Authorization: Bearer $ACCESS.$SECRET"

# 1. create the Match with the modalities from the club catalog
curl -s -X POST $BASE/precisao/provas -H "$AUTH" \
  -H 'Content-Type: application/json' \
  -d '{"nome":"1ª Etapa 2026","data_prova":"2026-03-14",
       "modalidades":["<uuid-da-modalidade>"],
       "external_id":"etapa-1-2026"}'

# 2. register an athlete in a modality and a relay
curl -s -X POST $BASE/precisao/provas/$prova/inscricoes -H "$AUTH" \
  -H 'Content-Type: application/json' \
  -d '{"prova_modalidade_uuid":"<uuid>","turno_uuid":"<uuid>",
       "nome":"Fulano","email":"[email protected]",
       "sexo":"M","data_nascimento":"1988-04-02",
       "external_id":"insc-4471"}'

# 3. open the Match and publish the results
curl -s -X PATCH $BASE/precisao/provas/$prova -H "$AUTH" \
  -H 'Content-Type: application/json' \
  -d '{"status":"aberta","resultados_publicos":true}'

# 4. read the ranking (grouped by modality, with the tie-break spelled out)
curl -s $BASE/precisao/provas/$prova/resultados -H "$AUTH"

external_id: the id from YOUR system

Every Match and registration resource accepts an external_id. Store the id of your own record in it and you find everything again without having to keep our uuids:

curl -s "$BASE/precisao/provas?external_id=etapa-1-2026" -H "$AUTH"

It is unique within its scope (club for Matches, Match for registrations), which also makes it an idempotency key: resending the same POST returns a conflict instead of silently creating a duplicate. Sending null unlinks it; omitting the field leaves what is stored untouched.

Scores are not written through here

The scorecard is born signed out in the field — the judge's HMAC, chained to the athlete's PIN when the Match requires it. A score POST would create a result without that chain, which is exactly what the module exists to prevent. The API reads the result; the one who produces it is the judge on the scoring screen.

For the same reason, the API never issues or returns an athlete's PIN. Only the path to erase it exists, so the athlete can create another one — the PIN is their secret, and an integrator who knew it would empty the athlete signature of any meaning.

What is blocked, and why

These rules hold the same on the screen and in the API. All of them return 409:

Attempt Why
Changing the tie-break or the PIN requirement with a score entered Both are part of what was signed; changing them would reorder the ranking with nobody shooting.
Changing the course of fire of the modality with a score entered The signed scorecard points to those targets.
Renumbering a series with a score entered The final-series tie-break reads the series in order.
Changing the modality of a registration that has a score It would move a signed scorecard to another course of fire.
Deleting a registration that has a score A signed scorecard would be left with no owner. Use the "cancelled" status.
Publishing a draft Match The structure is still going to change; the link would show something that does not hold.
Changing the structure of a closed Match The result has already circulated as official.
Going over the relay slot limit It is the same limit the screen respects.

Deleting a Match also requires its exact name in ?confirmacao= — the same danger zone as the interface.

Pagination, limits and errors

  • Listings accept ?page (from 1) and ?per_page (default 25, maximum 100; on registrations, default 50 and maximum 200) and return page, per_page and total.
  • Rate limit per key, with the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. Going over it returns 429 with Retry-After — respect it instead of retrying in a loop.
  • Errors are always JSON in the same shape: {"error": {"code": "not_found", "message": "Match not found."}}. The message is written to be read by whoever is integrating, not just logged.

Checking what the integration did

Every Match exposes its own audit trail, and the actions taken with a key show up there alongside those taken on the screen. This is how you reconcile what the robot did with what the match office did:

curl -s "$BASE/precisao/provas/$prova/auditoria?acao=precisao.prova_update" -H "$AUTH"

Interactive reference

Explore and try the endpoints below (Swagger UI). The OpenAPI specification JSON is at openapi.json.

This guide covers precision shooting Matches (rimfire and bullseye), along the lines of the SCTSL and CBTT rulebooks.