This project is maintained as part of the SKG-IF RDA Working Group. It is an implementation of the SKG-IF API for a set of DOI registration agencies. PR are welcome for any update :-).
The implementation exposes a REST API with metadata for any DataCite or Crossref DOI, sourced live from DataCite or Crossref - no local database, every request is served by calling the relevant upstream REST API and mapping the result onto a SKG-IF entity.
Provider selection is by URL path, not auto-detected: /datacite/products and /datacite/grants are DataCite-
backed, /crossref/products and /crossref/grants are Crossref-backed. The two providers are
independent, side-by-side implementations (org.skgif.doi.datacite / org.skgif.doi.crossref) -
there is no runtime DOI-registry lookup deciding which one to call.
Two SKG-IF entities are implemented per provider:
- Products (
GET /datacite/products/{local_identifier},GET /datacite/products, and the Crossref equivalents under/crossref/products) - every DOI except grants/awards.product_type(literature/research data/research software/other) is derived from DataCite'sresourceTypeGeneral(seeResourceTypeMapping) or Crossref'stype(seeCrossrefTypeMapping) - Crossref has nosoftwaretype, soresearch softwareis only reachable via the DataCite provider. - Grants (
GET /datacite/grants/{local_identifier},GET /datacite/grants, and the Crossref equivalents under/crossref/grants) - DataCite DOIs withresourceTypeGeneral: "Award", or Crossref DOIs withtype: "grant"(a grant/funding award registered as its own DOI).
See GETPRODUCTBYID_FLOW.md for a sequence-diagram walkthrough of a
single getProductById request, traced per provider (DataCite, Crossref, mEDRA) from the actual
resource/mapper code.
- JDK 25+ (declared once, in
pom.xml's<maven.compiler.release>) - Maven 3.9+ - or just use the bundled
./mvnwwrapper, which pins its own version in.mvn/wrapper/maven-wrapper.propertiesand needs no local Maven install - Network access to
api.datacite.organdapi.crossref.org(no API key needed for either - both are public, unauthenticated reads) - The SKG-IF API yaml reference is stored in
src/main/openapi/skg-if-openapi.yaml. - The
openapi-generator-maven-pluginis used to generate java implementation from the openapi yaml
To run this project in a preconfigured GitHub Codespace instead of locally, see DEPLOYMENT_CODESPACES.md.
mvn quarkus:devThis starts the API on http://localhost:8080. All endpoints are served under /skg-if/api
(derived from the SKG-IF OpenAPI spec's own path convention).
# a dataset, by DOI (its https://doi.org/... URL is the local_identifier)
curl http://localhost:8080/skg-if/api/datacite/products/10.15151/esrf-dc-2493599001
# a software DOI - product_type is derived from resourceTypeGeneral, not hardcoded
curl http://localhost:8080/skg-if/api/datacite/products/10.5281/zenodo.21826016
# an Award DOI - served under /datacite/grants, not /datacite/products
curl http://localhost:8080/skg-if/api/datacite/grants/10.71707/yj21-5d60
# a page of products (spans every DataCite prefix unless datacite.prefix is configured)
curl "http://localhost:8080/skg-if/api/datacite/products?page_size=5"
# filtering (see ProductFilters.java / GrantFilters.java for the supported subset)
curl "http://localhost:8080/skg-if/api/datacite/products?filter=cf.search.title:tomography"
# a Crossref-registered DOI, via the separate /crossref path
curl http://localhost:8080/skg-if/api/crossref/products/10.1038/nature12373
# a Crossref grant DOI (type: "grant") - served under /crossref/grants, not /crossref/products
curl http://localhost:8080/skg-if/api/crossref/grants/10.35802/218300Quarkus Dev UI (Swagger UI, config, CDI beans, etc.) is available at
http://localhost:8080/q/dev-ui while in dev mode.
mvn package
java -jar target/quarkus-app/quarkus-run.jarCI publishes a JVM-mode image to GitHub Container Registry on every push to main:
docker pull ghcr.io/skg-if/api-impl-doira:latest
docker run -p 8080:8080 ghcr.io/skg-if/api-impl-doira:latestThe base image tag is kept patched by Dependabot (.github/dependabot.yml), which also opens monthly grouped update PRs for Maven dependencies and GitHub Actions.
Pushing a vX.Y.Z git tag also publishes matching :X.Y.Z and :X.Y image tags, so a specific
release can be pinned instead of tracking :latest. Version tags are meant to be created once and
never force-moved to a different commit.
Optionally, set CROSSREF_MAILTO to identify this API to Crossref's "polite pool" for better
rate limits/uptime on /crossref/products and /crossref/grants (see
Configuration below):
docker run -p 8080:8080 -e CROSSREF_MAILTO=you@example.org ghcr.io/skg-if/api-impl-doira:latestTo deploy this image on the EOSC EU Node Tools Hub instead of running it yourself, see DEPLOYMENT_EOSC_NODE.md.
To build and test the image locally instead of relying on the published GHCR image:
mvn package
docker build -f src/main/docker/Dockerfile.jvm -t puma-skg-if-api:local .
docker run -p 8080:8080 puma-skg-if-api:local
curl "http://localhost:8080/skg-if/api/datacite/products?page_size=1"mvn testIncludes golden-file tests that compare the API's full JSON-LD response against committed
reference documents in src/test/resources/expected/. After an intentional change to
DataCiteToSkgIfMapper/CrossrefToSkgIfMapper (or anything else that changes the response
shape), regenerate the fixtures via ProductsGoldenTest/GrantsGoldenTest (each covers every
provider - DataCite, Crossref, and, for products, mEDRA):
mvn test -Dtest=ProductsGoldenTest,GrantsGoldenTest -Dgolden.regenerate=true
git diff src/test/resources/expected/ # review before committingIncludes mapper unit tests (against captured real fixtures in src/test/resources/ - DataCite
fixtures span a Dataset, a Software DOI, a Text DOI and an Award; Crossref fixtures span a
journal article, a dataset, a journal article with an abstract/funder, and a grant) and
@QuarkusTest resource tests that mock the respective REST client.
Key properties (src/main/resources/application.properties). Any of them can be overridden
without rebuilding the image via a docker run -e ... environment variable, per Quarkus/SmallRye
Config's standard mapping (dots to underscores, uppercased - e.g. crossref.mailto becomes
CROSSREF_MAILTO, see Docker image above):
| Property | Purpose |
|---|---|
datacite.api.base-url |
DataCite REST API base URL |
datacite.prefix |
Optional - scopes /datacite/products and /datacite/grants results to one DataCite DOI prefix (e.g. your own organisation's). Blank (default) means no restriction. |
crossref.api.base-url |
Crossref REST API base URL |
crossref.prefix |
Optional - scopes /crossref/products and /crossref/grants results to one DOI prefix, same convention as datacite.prefix. |
crossref.mailto |
Optional but recommended - identifies this API to Crossref's "polite pool" for better rate limits/uptime. Blank (default) means anonymous/non-polite-pool requests. Set via -e CROSSREF_MAILTO=you@example.org on docker run. |
skgif.local-identifier.base-url |
https://doi.org/ - prefixed onto every entity's DOI to form its SKG-IF local_identifier |
skgif.sandbox.base-url |
JSON-LD @context @base root - namespaced per-response to the DataCite client that registered the served DOI(s) (relationships.client.data.id, e.g. inist.esrf). Crossref has no equivalent concept mapped yet, so Crossref-backed responses always fall back to skgif.context.base. |
skgif.context.base |
Fallback @base, used when a DataCite DOI carries no client relationship, and always for Crossref-backed responses |
skgif.default-page-size |
Default /datacite/products and /datacite/grants page size (both providers) |
src/main/openapi/skg-if-openapi.yaml- vendored, version-pinned copy of the official SKG-IF OpenAPI spec (with one documented local patch, noted in the file's header comment)org.skgif.doi.generated.*- JAX-RS/model classes generated from that spec viaopenapi-generator-maven-plugin(models only are actually used - seeDataCiteProductsResource's javadoc for why the generatedProductApi/GrantApiinterfaces aren't implemented directly). Provider-agnostic - both DataCite and Crossref map onto these sameProduct/Grantclasses.org.skgif.doi.datacite- DataCite REST client, DTOs (datacite.dto),ResourceTypeMapping(DataCiteresourceTypeGeneral<-> SKG-IFproduct_type, and Award detection) and theDataCiteToSkgIfMapper(datacite.mapper) that maps DataCite records toProduct/Grantorg.skgif.doi.crossref- the Crossref sibling oforg.skgif.doi.datacite: REST client, DTOs (crossref.dto),CrossrefTypeMapping(Crossreftype<-> SKG-IFproduct_type, and grant-type detection) andCrossrefToSkgIfMapper(crossref.mapper)org.skgif.doi.rest- the shared JSON-LD envelope/pagination/filter-syntax helpers, plus one subpackage per provider holding its resources and filter parsing:rest.datacite(/datacite/products//datacite/grants,DataCiteProductFilters/DataCiteGrantFilters),rest.crossref(/crossref/products//crossref/grants,CrossrefFilters), andrest.medra(/medra/products). Provider selection is by which resource (and thus URL path) is hit - there is no runtime dispatcher.org.skgif.doi.util.LocalIdentifiers- DOI <->local_identifierconversionorg.skgif.doi.jackson- a Jackson mixin working around a generator quirk (see its javadoc)