Skip to content

CDLUC3/dmptool-api

dmptool-api - REST API interface for the DMP Tool.

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.

Table of Contents

Authentication

Endpoints

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+json this 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.

Query / Search for DMPs

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.

List/Search DMPs

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: public
  • created_before: type: string, format: date-time, example: 2026-04-08T13:02:21Z
  • created_after: type: string, format: date-time, example: 2026-04-08T13:02:21Z
  • modified_before: type: string, format: date-time, example: 2026-04-08T13:02:21Z
  • modified_after: type: string, format: date-time, example: 2026-04-08T13:02:21Z
  • languages: type: array of strings, format: 3 character language code ISO-639-3, default: eng
  • contact_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+physics
  • ethical_issues_exist: type: boolean
  • embargo_before: type: string, format: date, example: 2026-04-08
  • embargo_after: type: string, format: date, example: 2026-04-08
  • offset: type: integer, default: 0
  • count: type: integer, default: 20, max: 100
  • sort: 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>"

Get a specific DMP

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>"

Create a DMP

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_id property will become an alernate_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_id is 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_id can 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.

Replace a DMP

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"
             }]
           }
         }'

Delete a DMP

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"

Errors

The API returns the following error codes:

  • 400 bad_request When a header, query string or path parameter are missing or invalid
  • 400 dmp_invalid When the DMP JSON sent in the body is invalid
  • 401 unauthorized When you are not authorized to perform the action
  • 404 not_found When the endpoint is not found
  • 409 conflict When the timestamp you provided in the header is out of date
  • 429 too_many_requests When you have reached a rate limit threshold
  • 500 generic_error When the API encounters an internal error

DMP Format Examples

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_version the RDA standard that the record conforms to
  • provenance the system that created the DMP
  • status the current status of the DMP ["draft","complete","archived"]
  • privacy the privacy setting for the DMP ["public","private","embargoed"]
  • featured whether or not the DMP is featured on the "Public Plans page" of the DMP Tool UI
  • registered the date the DMP id was registered as a DOI with DataCite
  • research_domain the domain of the research project (e.g. "biology", "mathematics", etc.)
  • research_facility information about any research facilities, labs, etc. where the research was performed or data was collected
  • funding_opportunity the id of the funder's announcement or call for submissions. The project_id and funder_id here are used to tie the information to a specific funder in the main DMP record
  • funding_project the funder's identifier for the research project. The project_id and funder_id here are used to tie the information to a specific funder in the main DMP record
  • version the list of historical copies of the DMP and a URL to access them
  • narrative the 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"
        }]
      }]
    }]
  }
}

Exceptions to the RDA Common Standard format

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"
}

Installation

This API uses the Fastify framework, a low-overhead web framework for Node.js.

Prerequisites

  • Node.js 22.x
  • npm 11.x

Temporary prerequisites

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.

Development

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

Versioning

The system has two levels of versioning:

JSON Schema Versions

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.

API Versions

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

Contributing

Please read CONTRIBUTING.md for details on contributing. Please read CODE_OF_CONDUCT.md for details on our code of conduct.

License

This project is licensed under the MIT License - see the LICENSE for details.

About

REST API interface for the DMP Tool

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages