The SPARQL endpoint

SHMARQL is a SPARQL proxy: it accepts standard SPARQL 1.1 Protocol requests and forwards them to the triplestore of a profile. Clients only ever see your SHMARQL address; the real endpoint stays hidden and can be moved without breaking anyone's scripts.

Address Goes to
/sparql The default profile.
/sparql/<key> The profile with that key.

A profile's key is the first 12 hexadecimal characters of the SHA-256 hash of its endpoint address. It stays the same as long as the address does. List the keys with:

$ uv run shmarql profiles
* 479c34c9a197  wikidata             https://query.wikidata.org/sparql

* = served at /sparql; every profile is also served at /sparql/<key>

or open /api/profiles, which returns the same list as JSON (without the upstream addresses):

[
  {
    "key": "479c34c9a197",
    "name": "wikidata",
    "title": "Wikidata",
    "description": "The Wikidata Query Service (WDQS)",
    "sparql": "/sparql/479c34c9a197",
    "default": true
  }
]

Sending queries

Everything a SPARQL client normally does works:

  • GET with a query= parameter;
  • POST with a form-encoded body (query=…);
  • POST with Content-Type: application/sparql-query and the query as the body.

SHMARQL passes your Accept header on, so you choose the result format the upstream endpoint supports (JSON, XML, CSV, TSV, Turtle for CONSTRUCT, …). The status code, content type and body come back unchanged.

With curl

# Five paintings by Vermeer, as SPARQL JSON
curl -G http://127.0.0.1:8000/sparql \
  -H 'Accept: application/sparql-results+json' \
  --data-urlencode 'query=SELECT ?p WHERE { ?p wdt:P170 wd:Q41264 } LIMIT 5'

# The same as CSV, with POST
curl http://127.0.0.1:8000/sparql \
  -H 'Accept: text/csv' \
  --data-urlencode 'query=SELECT ?p WHERE { ?p wdt:P170 wd:Q41264 } LIMIT 5'

# A CONSTRUCT query as Turtle, sent to a specific profile
curl http://127.0.0.1:8000/sparql/479c34c9a197 \
  -H 'Content-Type: application/sparql-query' \
  -H 'Accept: text/turtle' \
  --data-binary 'CONSTRUCT { wd:Q41264 ?p ?o } WHERE { wd:Q41264 ?p ?o } LIMIT 20'

With Python

Using SPARQLWrapper:

from SPARQLWrapper import SPARQLWrapper, JSON

sparql = SPARQLWrapper("http://127.0.0.1:8000/sparql")
sparql.setReturnFormat(JSON)
sparql.setQuery("""
    SELECT ?painting ?paintingLabel ?year WHERE {
      ?painting wdt:P170 wd:Q41264 ; wdt:P571 ?date .
      BIND(YEAR(?date) AS ?year)
      SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
    }
    ORDER BY ?year
""")
for row in sparql.queryAndConvert()["results"]["bindings"]:
    print(row["year"]["value"], row["paintingLabel"]["value"])

Or with nothing but httpx (or requests):

import httpx

response = httpx.post(
    "http://127.0.0.1:8000/sparql",
    data={"query": "SELECT (COUNT(*) AS ?n) WHERE { ?p wdt:P170 wd:Q5598 }"},
    headers={"Accept": "application/sparql-results+json"},
    timeout=60,
)
print(response.json()["results"]["bindings"][0]["n"]["value"])

With JavaScript

const query = "SELECT ?p WHERE { ?p wdt:P170 wd:Q41264 } LIMIT 5";
const response = await fetch("/sparql", {
  method: "POST",
  headers: {
    "Content-Type": "application/sparql-query",
    Accept: "application/sparql-results+json",
  },
  body: query,
});
const json = await response.json();
console.log(json.results.bindings);

From another website, the browser only allows this when SHMARQL lists that site in CORS_ORIGINS (see Configuration).

In the browser

Open /sparql (or /sparql/<key>) in a browser without a query and SHMARQL sends you to the query editor, with that profile selected.

Updates are refused by default

SPARQL Update requests (INSERT, DELETE, …) are refused with 403 Forbidden, unless the profile sets allow_update: true. Updates must use POST. The APIs used by data stories and the story editor are always read-only, whatever the profile says.

Errors

Status Meaning
400 No query was sent.
403 An update was sent to a read-only profile.
404 Unknown profile key, or no default profile.
405 An update was sent with GET.
502 / 504 The upstream endpoint couldn't be reached or took longer than the profile's timeout.
other Passed through from the upstream endpoint, for example 400 for a syntax error or 429 when Wikidata asks you to slow down.