dragoman
A web server for scholarly PID (Persistent Identifier) resolution with full DOI content negotiation. Send a DOI as the URL path; receive a redirect to the landing page or metadata in any supported format depending on the Accept header.
Installation
Prerequisites
- Rust 1.75+ (rustup.rs)
Install
This builds a release binary and installs it to ~/.cargo/bin/dragoman. Make sure ~/.cargo/bin is on your PATH (the Rust installer adds this automatically).
Local SQLite database
dragoman can serve metadata directly from a local SQLite database in the commonmeta format, bypassing the live Crossref/DataCite APIs. This dramatically reduces latency and API load for high-traffic deployments.
Database format
The database is a SQLite3 file with a single works table whose columns map one-to-one to the commonmeta v1.0 schema. The id column is the canonical DOI URL (e.g. https://doi.org/10.5281/zenodo.1234). Complex fields (contributors, references, …) are stored as JSON text.
You can build a database from any commonmeta-supported source using the commonmeta CLI.
Running the server
Start
# Default port 3000
# Custom port
# With a local database
# Write a PID file so the process can be stopped later
# All options together
Options can also be supplied as environment variables (flags take precedence):
PORT=8080 DRAGOMAN_DB=/data/commonmeta-2026-06-15.sqlite3 RUST_LOG=dragoman=debug
During development you can use cargo run in place of the installed binary:
# Run from the project root — the sqlite3 file in the root is loaded by filename
# Or with a full path
Error: port already in use
If the chosen port is already in use, the server logs an error and exits:
ERROR dragoman: failed to bind port=3000 error=Address already in use (os error 48)
Choose a different port with --port or stop the process that holds the port.
Error: database file not found
If --db points to a path that does not exist, the server exits at startup before accepting any traffic:
ERROR dragoman: failed to open database path=… error=sqlite file not found: '…'
Pass the correct path or an absolute path to avoid working-directory ambiguity.
Stop
# Stop using the default PID file location (/tmp/dragoman.pid)
# Stop using a custom PID file
dragoman stop sends SIGTERM to the running process. The server handles the signal gracefully: it finishes in-flight requests and removes the PID file before exiting. Pressing Ctrl-C has the same effect.
CLI reference
dragoman <COMMAND>
Commands:
start Start the server (runs in the foreground)
stop Stop a running server by sending SIGTERM to its PID file
help Print help
dragoman start [OPTIONS]
-p, --port <PORT> TCP port to listen on [env: PORT] [default: 3000]
-d, --db <PATH> Local commonmeta SQLite3 database [env: DRAGOMAN_DB]
--pid-file <PATH> Write PID to this file on startup [env: DRAGOMAN_PID_FILE]
dragoman stop [OPTIONS]
--pid-file <PATH> PID file to read [env: DRAGOMAN_PID_FILE] [default: /tmp/dragoman.pid]
Environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
TCP port to listen on. |
DRAGOMAN_DB |
(none) | Path to a local commonmeta SQLite3 database. Metadata is served from the database before falling back to the live API. The server exits on startup if the path is set but the file cannot be opened. |
DRAGOMAN_PID_FILE |
(none) | Path for the PID file written by start and read by stop. |
RUST_LOG |
dragoman=info |
Log filter (see tracing-subscriber). Use dragoman=debug to log per-request cache hits. |
Usage
Redirect (HTML / browser)
When the Accept header prefers text/html or is absent, dragoman redirects to the DOI's landing page:
# Follow the redirect
# Inspect the redirect target without following
# https://zenodo.org/record/1089100
Content negotiation
Send an Accept header to receive metadata instead of a redirect.
BibTeX
CSL-JSON
DataCite JSON
RIS
Crossref XML
Schema.org JSON-LD
Formatted citation
text/x-bibliography accepts optional style= and locale= parameters. Style names come from the CSL style repository; locale codes from the CSL locales repository.
# APA (default)
# Vancouver in French
Query parameter overrides
Use ?format= instead of an Accept header:
Force a specific registration agency (useful for testing):
Supported formats
| Accept header | ?format= value |
Notes |
|---|---|---|
application/vnd.citationstyles.csl+json |
csl |
|
application/vnd.commonmeta+json |
commonmeta |
|
application/vnd.datacite.datacite+json |
datacite |
|
application/vnd.datacite.datacite+xml |
datacite_xml |
|
application/vnd.crossref.unixref+xml |
crossref_xml |
|
application/vnd.crossref.unixsd+xml |
crossref_xml |
alias |
application/x-bibtex |
bibtex |
|
application/x-research-info-systems |
ris |
|
application/vnd.schemaorg.ld+json |
schemaorg |
|
text/x-bibliography |
citation |
style= and locale= params |
text/html / (absent) |
— | 307 redirect to landing page |
HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Metadata returned |
| 307 | Redirect to landing page |
| 404 | DOI not found |
| 406 | Requested content type not supported |
| 502 | Upstream API error |
License
MIT