This is the REST API for the DMP Tool system. It follows the RDA Common API Specification with support for the RDA Common Standard for maDMPs v1.2 and optionally, the DMP Tool extensions to that standard.
Current Version: 3.0.0
See the CHANGELOG.md for details
To access the Swagger UI: <SWAGGER ENDPOINT>.
Note: that the Swagger UI is currently not loading the routes properly and we have not deployed to the AWS env.
- Authentication
- Endpoints
- Errors
- DMP Examples
- DMP Tool Exceptions to the RDA Common Standard
- Installation
- Contributing
- License
For the majority of endpoints, you can specify the format you would like to receive. See below for examples of each format.
- For the RDA Common metadata standard use
Accept: application/vnd.org.rd-alliance.dmp-common.v1.2+jsonthis does not include the DMP Tool specific extensions. - To receive the full DMP with DMP Tool extensions you should use:
Accept: application/vnd.org.dmptool.v1.2+json. - If you specify any other type, the API will return the RDA Common standard WITHOUT DMP Tool extensions by default.
Note: All DMP Ids that are included in the path of a request MUST be URL encoded. For example use /dmps/10.10000%2FDMP123 instead of /dmps/10.10000/DMP123.
The GET /api/v3/dmps endpoint lists all DMPs or allows for creating a filtered list of DMPs.
Public access: When an authorization token is NOT provided, the API will only return DMPs who's privacy setting is "public".
Authorized access:
When you provide a valid authorization token, you can set the scope argument to return either your own DMPs, your affiliation's DMPs or all DMPs (public, affiliation and your DMPs).
Success Response: 200
Query string arguments: When filters are provided, all filters are applied using an "AND" relationship. For filters supporting lists, the individual values are applied using an "OR" relationship. For items accepting more than one value you may pass multiple values by repeating the parameter in the query string for each item.
For example, if you specify ?created_after=2020-01-01T11:19:23Z&contact_ids=12345&contact_ids=09876 will be treated as "Created after '2020-01-01T11:19:23' AND has a contact with an id of '12345' OR '09876'".
scope- type:string, enum:['mine', 'affiliation', 'public'], default:publiccreated_before: type:string, format:date-time, example:2026-04-08T13:02:21Zcreated_after: type:string, format:date-time, example:2026-04-08T13:02:21Zmodified_before: type:string, format:date-time, example:2026-04-08T13:02:21Zmodified_after: type:string, format:date-time, example:2026-04-08T13:02:21Zlanguages: type:array of strings, format: 3 character language code ISO-639-3, default:engcontact_ids: type:array of strings, example:['123', '0000-0000-0000-0000', 'user@example.com']contributor_ids: type:array of strings, example:['123', '0000-0000-0000-0000', 'user@example.com']dataset_ids: type:array of strings, example:['123', 'https://doi.org/00.00000/A1test', 'https://dataset.example.com/123']metadata_standard_ids: type:array of strings, example:['123', 'https://standard.example.com/123']dmp_ids: type:array of strings, example:['123', '00.0000/A1test']funder_ids: type:array of strings, example:['123', 'https://ror.org/12345']grant_ids: type:array of strings, example:['123', 'https://grants.example.com/123']query: type:string, example:particle+physicsethical_issues_exist: type:booleanembargo_before: type:string, format:date, example:2026-04-08embargo_after: type:string, format:date, example:2026-04-08offset: type:integer, default:0count: type:integer, default:20, max:100sort: type:string, enum:['title,asc', 'title,desc', 'created,asc', 'created,desc', 'modified,asc', 'modified,desc', 'language,asc', 'language,desc', 'embargo,asc', 'embargo,desc', 'keyword,asc', 'keyword,desc'], default:created,desc
The scope argument determines what type of DMPs will appear in the results. public returns only public DMPs mine returns only the DMPs that belong to you (the authorization token must be present), affiliation returns only the DMPs that belong to your affiliation (the authorization token must be provided and you must be an administrator).
Examples:
# Query for all public DMPs modified after January 1, 2026
curl -v "http://localhost:4060/api/v3/dmps?modified_after=2026-01-01T00:00:00"
# Query for all public DMPs modified after January 1, 2026 that are associated with a specific contributor and are about particle physics
curl -v "http://localhost:4060/api/v3/dmps?modified_after=2026-01-01T00:00:00&contriutor_ids=0000-0000-0000-0000&query=particle+physics"
# Query for you DMPs
curl -v "http://localhost:4060/api/v3/dmps?scope=mine" -H "Authorization: Bearer <token>"The GET /api/v3/dmps/:id endpoint returns a DMP.
Public access: When an authorization token is NOT provided, the API will only return DMPs who's privacy setting is "public". If you request a DMP that exists but that you do not have access to, you will receive a 404 Not Found error.
Authorized access: When you provide a valid authorization token, you can request public DMPs, DMPs you own, or DMPs that are associated with your affiliation if you are an administrator.
You can also request historical versions of a DMP by including the ?version=2026-01-01T13:12:11Z query string. The timestamp must match the value of a known historical version. To see a DMP's historical version list, call this endpoint (without the version) and review its version array property.
Success Response: 200
Examples:
# Query for a public DMP and receive the RDA Common Standard format without DMP Tool extensions
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1123"
# OR
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1123" -H "Accept: application/vnd.org.rd-alliance.dmp-common.v1.2+json"
# Query for a public DMP and receive the RDA Common Standard format with DMP Tool extensions
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1123" -H "Accept: application/vnd.org.dmptool.v1.2+json"
# Query for a private DMP you own
# Query for you DMPs
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1987" -H "Authorization: Bearer <token>"The POST /api/v3/dmps endpoint allows you to create a DMP.
You must provide a DMP as JSON metadata that conforms to the RDA Common Standard for maDMPs. The JSON metadata should include DMP Tool extensions if possible.
Note: You MUST provide an authorization token for this endpoint
Success Response: 201
Example:
curl -v "http://localhost:4060/api/v3/dmps" \
-X POST
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/vnd.org.dmptool.v1.2+json" \
-D '{
"dmp": {
"title": "Test DMP",
"dmp_id": { "identifier": "local-system-id", "type": "other" },
"created": "2021-01-01 03:11:23Z",
"modified": "2021-01-01 02:23:11Z",
"ethical_issues_exist": "unknown",
"language": "eng",
"contact": {
"name": "Test Contact",
"mbox": "tester@example.com",
"contact_id": [{ "identifier": "123456789", "type": "other" }]
},
"dataset": [{
"title": "Test Dataset",
"dataset_id": { "identifier": "123", "type": "other" },
"personal_data": "unknown",
"sensitive_data": "no"
}]
}
}'
Some notes about the DMP format above:
- The minimal viable RDA Common Standard record with DMP Tool extensions is shown
- The API will ignore the values you send for
modified. It will set this to the current time. - The API will generate a new DMP id for the record. The one you provide in the
dmp_idproperty will become analernate_identifier(see: the https://github.com/RDA-DMP-Common/RDA-DMP-Common-Standard?tab=readme-ov-file#alternate_identifier_table) for the DMP. It's a good idea to set this to a URL or identifier from your own system. When you retrieve the DMP from this API, it will include this alternate identifier which you can then use to tie the record back to your system. - The preferred
contact_idis an ORCID, but you can also supply an email or an internal identifier that you can use to cross-reference with your own system. - The
dataset_idcan either be a DOI or URL of a published output OR an internal identifier that you can use to cross-reference with your own system.
The PUT /api/v3/dmps/:id endpoint allows you to replace the current DMP with the metadata you provide. If you just want to update a portion of a DMP record, you should call the GET /api/v3/dmps/:id endpoint first to retrieve the full DMP record. Then modify it as needed and send the update JSON record to this endpoint.
You must provide a DMP as JSON metadata that conforms to the RDA Common Standard for maDMPs. The JSON metadata should include DMP Tool extensions if possible.
You also need to supply the If-Unmodified-Since header. The value of this header should match the modified timestamp on the original DMP record. If the timestamp in your header does not match the modified timestamp when you submit your update, you will receive a 409 Conflict error. this means that the copy of the DMP that you have has been modified since you retrieved it. You should then refetch the DMP and apply your changes to the more recent version.
Note: You MUST provide an authorization token for this endpoint
Success Response: 200
Example:
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1123" \
-X PUT \
-H "Authorization: Bearer <token>" \
-H "If-Unmodified-Since: 2021-01-01 02:23:11Z" \
-H "Content-Type: application/vnd.org.dmptool.v1.2+json" \
-D '{
"dmp": {
"title": "Updated title",
"dmp_id": { "identifier": "local-system-id", "type": "other" },
"created": "2021-01-01 03:11:23Z",
"modified": "2021-01-01 02:23:11Z",
"ethical_issues_exist": "no",
"language": "eng",
"contact": {
"name": "Test Contact",
"mbox": "tester@example.com",
"contact_id": [{ "identifier": "123456789", "type": "other" }]
},
"dataset": [{
"title": "Test Dataset",
"dataset_id": { "identifier": "123", "type": "other" },
"personal_data": "unknown",
"sensitive_data": "no"
}]
}
}'The DELETE /api/v3/dmps endpoint allows you to replace the current DMP with the metadata you provide. If you just want to update a portion of a DMP record, you should call the GET /api/v3/dmps/:id endpoint first to retrieve the full DMP record. Then modify it as needed and send the update JSON record to this endpoint.
You must be either the owner/creator of the DMP or an administrator at the affiliation associated with the DMP in order to delete it.
The DMP cannot be deleted if it has a registered timestamp. That timestamp indicates that the DMP id is a registered DOI and so we must retain it. In this scenario, the DMP will be tomb-stoned and its title will be prefixed with "OBSOLETE:".
You also need to supply the If-Unmodified-Since header. The value of this header should match the modified timestamp on the original DMP record. If the timestamp in your header does not match the modified timestamp when you submit your update, you will receive a 409 Conflict error. this means that the copy of the DMP that you have has been modified since you retrieved it. You should then refetch the DMP and apply your changes to the more recent version.
Note: You MUST provide an authorization token for this endpoint
Success Response: 204
Example:
curl -v "http://localhost:4060/api/v3/dmps/00.00000%2FA1123" \
-X DELETE
-H "Authorization: Bearer <token>" \
-H "If-Unmodified-Since: 2021-01-01 02:23:11Z"The API returns the following error codes:
400 bad_requestWhen a header, query string or path parameter are missing or invalid400 dmp_invalidWhen the DMP JSON sent in the body is invalid401 unauthorizedWhen you are not authorized to perform the action404 not_foundWhen the endpoint is not found409 conflictWhen the timestamp you provided in the header is out of date429 too_many_requestsWhen you have reached a rate limit threshold500 generic_errorWhen the API encounters an internal error
Minimum viable DMP
{
"dmp": {
"title": "Test DMP",
"dmp_id": { "identifier": "local-system-id", "type": "other" },
"created": "2021-01-01 03:11:23Z",
"modified": "2021-01-01 02:23:11Z",
"ethical_issues_exist": "unknown",
"language": "eng",
"contact": {
"name": "Test Contact",
"mbox": "tester@example.com",
"contact_id": [{ "identifier": "123456789", "type": "other" }]
},
"dataset": [{
"title": "Test Dataset",
"dataset_id": { "identifier": "123", "type": "other" },
"personal_data": "unknown",
"sensitive_data": "no"
}]
}
}Full RDA Common standard WITH DMP Tool specific extensions. The example here includes ALL the properties (see below for descriptions) from the RDA Common Standard record below along with:
{
"rda_schema_version": "1.2",
"provenance": "your-system-id",
"status": "complete",
"privacy": "private",
"featured": "no",
"registered": "2026-01-01T10:32:45Z",
"research_domain": {
"name": "biology",
"research_domain_identifier": {
"identifier": "https://example.com/01234567",
"type": "url"
}
},
"research_facility": [{
"name": "Ocean buoy 2345325",
"type": "field_station",
"research_facility_identifier": {
"identifier": "https://example.com/01234567",
"type": "url"
}
}],
"funding_opportunity": [{
"project_id": {
"identifier": "local-system-project-id",
"type": "other"
},
"funder_id": {
"identifier": "https://ror.org/0987654321",
"type": "ror"
},
"opportunity_identifier": {
"identifier": "https://example.com/01234567",
"type": "url"
}
}],
"funding_project": [{
"project_id": {
"identifier": "local-system-project-id",
"type": "other"
},
"funder_id": {
"identifier": "https://ror.org/0987654321",
"type": "ror"
},
"project_identifier": {
"identifier": "https://example.com/erbgierg",
"type": "url"
}
}],
"version": [{
"access_url": "https://example.com/api/v3/dmps/123456789?version=2026-01-01T10:32:45Z",
"version": "2026-01-01T10:32:45Z"
}],
"narrative": {
"download_url": "https://example.com/dmps/123456789/narrative",
"template": {
"id": 1234567,
"title": "Funder Template",
"description": "This is a test funder template for a DMP narrative",
"version": "v1",
"section": [{
"id": 9876,
"title": "Section one",
"description": "The first section of the narrative",
"order": 1,
"question": [{
"id": 1234,
"text": "Where will you deposit this output?",
"order": 1,
"answer": {
"id": 543,
"json": {
"type": "repositorySearch",
"answer": [{
"repositoryId": "https://example.com/repository/123456789",
"repositoryName": "Example Repository"
}],
"meta": { "schemaVersion": "1.0" }
}
}
}]
}]
}
}
}Explanation:
rda_schema_versionthe RDA standard that the record conforms toprovenancethe system that created the DMPstatusthe current status of the DMP["draft","complete","archived"]privacythe privacy setting for the DMP["public","private","embargoed"]featuredwhether or not the DMP is featured on the "Public Plans page" of the DMP Tool UIregisteredthe date the DMP id was registered as a DOI with DataCiteresearch_domainthe domain of the research project (e.g. "biology", "mathematics", etc.)research_facilityinformation about any research facilities, labs, etc. where the research was performed or data was collectedfunding_opportunitythe id of the funder's announcement or call for submissions. Theproject_idandfunder_idhere are used to tie the information to a specific funder in the main DMP recordfunding_projectthe funder's identifier for the research project. Theproject_idandfunder_idhere are used to tie the information to a specific funder in the main DMP recordversionthe list of historical copies of the DMP and a URL to access themnarrativethe DMP narrative content including a URL to access a PDF version.
Note: The narrative, status, research_facility list, funding_opportunity and funding_project information are only accessible if the DMP's privacy is public OR you have access to the DMP!
Full RDA Common Standard WITHOUT DMP Tool extensions.
{
"dmp": {
"title": "Test DMP",
"description": "Abstract for the DMP",
"dmp_id": {
"identifier": "local-system-id",
"type": "other"
},
"created": "2021-01-01 03:11:23Z",
"modified": "2021-01-01 02:23:11Z",
"ethical_issues_exist": "yes",
"ethical_issues_description": "This DMP contains ethical issues",
"ethical_issues_report": "https://example.com/ethical-issues-report",
"language": "eng",
"contact": {
"name": "Test Contact",
"mbox": "tester@example.com",
"affiliation": [
{
"name": "Test University",
"affiliation_id": {
"identifier": "https://ror.org/01234567890",
"type": "ror"
}
}
],
"contact_id": [
{
"identifier": "https://orcid.org/0000-0000-0000-0000",
"type": "orcid"
}
]
},
"contributor": [
{
"name": "Test Contact",
"contributor_id": [
{
"identifier": "https://orcid.org/0000-0000-0000-0000",
"type": "orcid"
}
],
"affiliation": [
{
"name": "Test University",
"affiliation_id": {
"identifier": "https://ror.org/01234567890",
"type": "ror"
}
}
],
"role": [
"https://example.com/roles/investigation",
"https://example.com/roles/other"
]
},
{
"name": "Someone else",
"contributor_id": [
{
"identifier": "852486334534",
"type": "other"
}
],
"affiliation": [
{
"name": "Test University",
"affiliation_id": {
"identifier": "https://ror.org/01234567890",
"type": "ror"
}
}
],
"role": [
"https://example.com/roles/data_curation"
]
}
],
"dataset": [
{
"title": "Test Dataset",
"type": "dataset",
"description": "This is our first dataset for the project",
"dataset_id": {
"identifier": "123",
"type": "other"
},
"personal_data": "unknown",
"sensitive_data": "no",
"issued": "2026-01-03",
"metadata": [
{
"description": "Description of metadata",
"language": "eng",
"metadata_standard_id": [
{
"identifier": "https://example.com/metadata-standards/123",
"type": "url"
}
]
}
],
"distribution": [{
"title": "Test Distribution",
"byte_size": 1234567890,
"issued": "2026-01-03",
"data_access": "open",
"license": [{
"license_ref": "https://spdx.org/licenses/CC-BY-4.0.html",
"start_date": "2026-04-01"
}],
"host": {
"title": "Zenodo",
"url": "https://zenodo.org",
"host_id": [{
"identifier": "https://www.re3data.org/repository/r3d100010468",
"type": "url"
}]
}
}]
}
],
"related_identifier": [{
"identifier": "https://doi.org/00.0000/dataset.123456789",
"relation_type": "cites",
"resource_type": "dataset",
"type": "doi"
}],
"alternate_identifier": [{
"identifier": "https://example.com/id-from-your-system",
"type": "url"
}],
"project": [{
"title": "test research project",
"description": "Project abstract ...",
"project_id": [{
"identifier": "local-system-project-id",
"type": "other"
}],
"start": "2025-01-01",
"end": "2028-12-31",
"funding": [{
"name": "Funder Organization",
"funding_status": "granted",
"funder_id": {
"identifier": "https://ror.org/0987654321",
"type": "ror"
},
"grant_id": [{
"identifier": "1234567890",
"type": "other"
}]
}]
}]
}
}The DMP Tool is structured differently than the RDA Common Standard with regard to DMPs and Projects. The RDA Common Standard allows for a single DMP to have multiple projects associated with it. The DMP Tool however, allows for multiple DMPs to be associated with a single project. Because of this the DMP metadata that the API works with will always have a 1-to-1 relationship between DMP and project.
The DMP Tool also differs from the RD Common Standard in the following ways:
We do NOT currently support the cost property:
{
"cost": [
{
"title": "Budget Cost",
"description": "Description of budget costs",
"value": 1234.56,
"currency_code": "USD"
}
]
}We do NOT support the following properties on a dataset:
{
"data_quality_assurance": [
"Statement about data quality assurance"
],
"is_reused": false,
"keyword": [
"test",
"physics"
],
"language": "eng",
"preservation_statement": "Statement about preservation",
"security_and_privacy": [
{
"title": "Security and Privacy Statement",
"description": "Description of security and privacy statement"
}
],
"alternate_identifier": [
{
"identifier": "https://example.com/dataset/123",
"type": "url"
}
],
"technical_resource": [
{
"name": "Telescope",
"description": "This is a description of the telescope",
"technical_resource_id": [
{
"identifier": "https://example.com/telescope/123",
"type": "url"
}
]
}
]
}We do NOT support the following properties of distribution. The typical workflow for the DMP Tool is to have user's describe the research outputs they plan to produce as part of their project. Since the outputs have not bee created, there are now access URLs available:
{
"description": "This is a test distribution",
"access_url": "https://example.com/dataset/123/distribution/123456789",
"download_url": "https://example.com/dataset/123/distribution/123456789/download",
"format": ["application/zip"]
}We do NOT support the following properties of host. We attempt to make use of registries like re3data when possible. Those registries maintain their own metadata and we prefer to use them as the source of truth:
{
"description": "This is a test host",
"availability": "99.99",
"backup_frequency": "weekly",
"backup_type": "tapes",
"certified_with": "coretrustseal",
"geo_location": "US",
"pid_system": ["doi", "ark"],
"storage_type": "LTO-8 tape",
"support_versioning": "yes"
}This API uses the Fastify framework, a low-overhead web framework for Node.js.
- Node.js 22.x
- npm 11.x
The following repositories are required to generate an access token which can be used to test out the JWT logic in the AuthPlugin. This will be replaced with logic to work with the cookie/token generated by the Rails app. We may need to modify the Rails app to generate the information needed.
- The DMP Tool Apollo server backend (see dmptool-apollo-server)
- The DMP Tool UI (see dmptool-ui)
A Husky pre-commit hook is configured to run the linter and tests and security audits before each commit.
To start up the application for local development npm run dev.
To run the linter npm run lint.
To run tests npm run test.
To build the application npm run build.
To check for dependency vulnerabilities npm audit and npm run trivy-all.
To access the Swagger UI from the instance running locally visit: http://localhost:4060/api/v3/documentation.
Note: that the Swagger UI is currently not loading the routes properly
The system has two levels of versioning:
The JSON schema versions are tied to the RDA Common Standard for maDMPs. We currently support v1.2. When the RDA Common Standard is updated, we need to update the @dmptool/types package to support those changes. Once those changes are made, the DMP Tool extensions schema should be updated so that it's version matches the RDA Common Standard. For example, if the RDA publishes v1.3 then the updated extension schema should be versioned as v1.3.
JSON schema versions are supported via content type negotiation. We currently support:
- RDA Common Standard v1.2
application/vnd.org.rd-alliance.dmp-common.v1.2+json(DEFAULT) - RDA Common Standard with DMP Tool Extensions v1.2
application/vnd.org.dmptool.v1.2+json.
If new versions of these schemas are created and the structure of the API does not change, then we can simply add the new versions to the src/serialization.ts file's content negotiation logic to begin supporting the new versions.
Other changes that would not break the API include:
- Adding a new endpoint
- Adding a new query parameter
- Dropping support for a specific version of the JSON schemas (assuming we have provided a long enough deprecation period)
When we do drop support for a specific version of the JSON schemas, we should add a Sunset header to the response to indicate when the schema will be removed from the API. We should, of course, also send out notifications to our contacts via email to let them know of the change.
An example of a Sunset header is:
Content-Type: application/vnd.org.rd-alliance.dmp-common.v1.2+json
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Once the sunset date is reached, we should change the default content type and remove the old content type from the list of supported accept headers.
We use a major-version-only scheme for versioning the API. We do this through the path (e.g. /api/v3/dmps).
The version number is only incremented when one of the following occurs:
- Change to the format of an existing endpoint
- Change to the format of the response body
- Change to the format of the request body
- Removing or changing an existing query parameter
Please read CONTRIBUTING.md for details on contributing. Please read CODE_OF_CONDUCT.md for details on our code of conduct.
This project is licensed under the MIT License - see the LICENSE for details.