[2.11] - 2026-07-28
Durable links for programmatic publication: a dataset published through
the API can now be followed across its whole life, from the first version
to the newest one, and can be handed over to another account.
Added
- Zenodo concept DOI stored in
ds_meta_data.dataset_concept_doi(new
migrationb8c9d0e1f2a3, nullable so older records keep working). It
is read on first publish and on every new version, and every version of
a lineage shares it.ZenodoService.get_concept_doiexposes it, with a
fallback that derives it fromconceptrecidwhen Zenodo omits
conceptdoi. GET /api/v1/datasets/doi/<doi>now also returnsconcept_doi,
version,total_versions,is_latest,latestandversions_url,
so a client holding an old DOI can discover the newest version. The
existing keys are untouched.GET /api/v1/datasets/<id>/versions, returning the whole version
lineage oldest first, with dataset id, version, DOI and publication
date for each entry.GET /api/v1/datasets/concept-doi/<concept_doi>, which resolves a
lineage (and therefore the latest version) from the stable concept DOI.POST /api/v1/datasets/<id>/transfer, which offers a dataset to
another account byuser_idoremail, restricted to the current
owner. It returns202with a pending offer and moves nothing on its
own. The receiving account answers through
POST /api/v1/datasets/transfers/<id>/acceptor/decline, the sender
can withdraw it with/cancel, and both sides can list their offers
withGET /api/v1/datasets/transfers. On acceptance the whole version
lineage moves as a unit, files included. A dataset carries a permanent
Zenodo DOI and appears in its owner's public listings, so it is never
pushed onto an account that did not ask for it.POST /api/v1/datasets/uploadaccepts optionalauthors(JSON array,
or the usualauthors[0][name]form fields), so a service account can
publish while crediting the real developer. The authors are stored as
Authorrows and become the Zenodo creators. Without them the previous
behaviour is unchanged. Names and affiliations are stripped of control
and bidirectional characters, anorcidis only accepted when that
ORCID has signed in to uvlhub at least once, and the publishing account
is recorded inds_meta_data.api_publisher_user_id(new migration
c9d0e1f2a3b4) and published in the Zenodo record's notes, so credit
given to somebody else stays traceable.concept_doiin the publish and new-version API responses.
Fixed
- Descriptions sent to
POST /api/v1/datasets/uploadare sanitized with
the same allowlist as the web upload. The dataset page renders the
description withsafe, so the API path was an unsanitized HTML sink
on a public DOI landing page. - Versioning a dataset that already has a successor is refused, so a
lineage cannot branch, and the lineage walk covers every branch of rows
that already did. A partial walk would hide published versions from
/versionsand move only part of a lineage on a transfer. - A version clone whose Zenodo publication fails is removed instead of
staying in the lineage forever with no DOI, and it no longer inherits
the concept DOI of its origin before Zenodo has assigned one. The
removal stops at the point of no return. Publishing a deposition mints
a permanent public DOI that nobody can withdraw, so the deposition id
is now committed before that call and the local row is kept afterwards
whatever else fails. Deleting it would have left a public Zenodo record
holding the user's files with no local trace of which deposition it is,
and no way to reconcile the two. - Persisting the concept DOI can no longer fail a publication that
already succeeded, and a concept DOI too long for the column is
dropped rather than breaking the write. user_idon a transfer is parsed strictly, so a JSONtrueor1.9
is rejected instead of being read as account 1.- An
orcidthat is not a string in an API author payload is rejected
with400instead of raising and returning500. - API keys stop working when their account is deactivated. The key used
to keep fullwrite_datasetpower indefinitely, which includes
publishing permanent public records to Zenodo. dataset_transfer_request.dataset_idcascades on delete, and the
rosemary db:delete-datasetcleanup removes transfer offers. Deleting
a dataset that had ever been offered failed with a foreign key error.- Migration
c9d0e1f2a3b4can be downgraded on MySQL and MariaDB. It
dropped the indexes backing its foreign keys before the table, which
those engines refuse, leaving the schema pinned at that revision.