OpenPinsheet

The API

Version 1, a draft. Two ways in: a live area for apps without a key, and submissions for data partners with a key. Closed days are files, see Use the files.

Today's pins: GET /v1/live

The current pins in an area, while the day is still open. No key, and any website may call it (CORS *).

Request
GET https://api.openpinsheet.com/v1/live?bbox=<west>,<south>,<east>,<north>
  • The box is at most 0.5 degrees wide and 0.5 degrees high, otherwise 400 bbox_too_large.
  • A box cannot cross the 180th meridian; query both sides separately.
  • A ground without any coordinate does not appear here, only in the files.
  • For every data partner and ground in the box, only its most recent visible day is returned.
  • A day's pins become visible at about 07:00 local time, not when they are sent: at a small club the time the pins were set is one person's working hours.
  • Cached for five minutes.

The answer is a GeoJSON FeatureCollection like a published file (Format), with format, as_of, license, attribution, notes and features. Live records can still change until the day closes; for anything you keep, use the files.

curl
curl 'https://api.openpinsheet.com/v1/live?bbox=13.0,52.0,13.1,52.1'
JavaScript
const box = "13.0,52.0,13.1,52.1";
const url = "https://api.openpinsheet.com/v1/live?bbox=" + box;
const today = await (await fetch(url)).json();
for (const feature of today.features) {
  console.log(feature.properties.hole, feature.properties.source,
              feature.geometry && feature.geometry.coordinates);
}
Answer, shortened
{ "type": "FeatureCollection", "format": 1, "as_of": "2027-04-12",
  "license": { "name": "Open Database License 1.0", "spdx": "ODbL-1.0", "url": "..." },
  "attribution": "Pin positions: OpenPinsheet and its channels ...",
  "notes": [ "..." ],
  "features": [
    { "type": "Feature",
      "geometry": { "type": "Point", "coordinates": [13.1234573, 52.1234561] },
      "properties": { "date": "2027-04-12", "date_basis": "local", "hole": "7",
                      "source": "estimated", "front_m": 12, "left_m": 9.5,
                      "channel": "..." } }
  ] }

Deliver pins: POST /v1/submissions

A data partner delivers the pins of one ground for one day, with a key. A key belongs to one partner; ask for one at info@openyardage.com. Partners agree that the pins they deliver are under the Open Database License 1.0.

curl
curl -X POST https://api.openpinsheet.com/v1/submissions \
  -H "Authorization: Bearer $OPENPINSHEET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "format": 1, "group": "w620181682", "date": "2027-04-12",
        "pins": [ { "hole": "7", "lat": 52.1234561, "lon": 13.1234573,
                    "source": "estimated", "front_m": 12, "left_m": 9.5,
                    "input": { "front": 12, "offset": 5, "side": "right", "unit": "m" } },
                  { "hole": "8", "missing": true } ] }'

Fields

  • format: the integer 1.
  • group: your own label for one ground, 1 to 64 characters A-Z a-z 0-9 . _ : -. It is never published; it only tells OpenPinsheet which earlier submission a new one replaces.
  • date: the calendar date at the ground, YYYY-MM-DD.
  • pins: 1 to 150 entries, each hole at most once. A pin carries the columns of the format except date, date_basis and channel; a lost position is { "hole": "9", "missing": true }.

Checks

  • Every pin passes the rules of the format.
  • date lies within one day of today's UTC date, and the day is still open.
  • All pins with a coordinate lie within 5 km of each other: one submission is one ground.
  • The body is at most 64 KiB. If any check fails, nothing is stored.

Replacing and withdrawing

A submission replaces everything the same partner sent for the same group and date; holes it does not name are gone afterwards. To correct a day, send the whole day again. To withdraw it while the day is open:

curl
curl -X DELETE "https://api.openpinsheet.com/v1/submissions?group=w620181682&date=2027-04-12" \
  -H "Authorization: Bearer $OPENPINSHEET_KEY"

A day closes at D + 3 days, 00:00 UTC and is then written to the files. After that it cannot be replaced or withdrawn here.

Answers

200
{ "stored": 3, "group": "w620181682", "date": "2027-04-12",
  "replaced": false, "visible_from": "2027-04-12T06:00:00Z" }

With ?dry_run=1 everything is checked and nothing is stored: the answer says "stored": 0 and "dry_run": true.

Errors carry { "error": "<code>", "message": "<sentence>" }:

StatuserrorMeaning
400invalid_jsonthe body is not JSON
401unauthorizedkey missing, unknown or revoked; always the same answer
409day_closedDELETE only: the day is already published. A POST for a closed day fails the date check (422).
413body_too_largemore than 64 KiB
422invalid_submissionproblems lists each problem with hole, field and message

Become a data partner

A key belongs to one data partner, and OpenPinsheet stores only its sha256. At most two valid keys exist per partner, so a key can be swapped without a gap. Keys are issued and revoked by the maintainer. Write to info@openyardage.com.

A data partner agrees to the terms of OpenPinsheet, among them that the pins it delivers are under the Open Database License 1.0, before it gets a key.