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-queryand 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. |