Skip to content

Records

B2INST Records API#

The routes, request bodies, and response codes below were checked against the B2INST inveniordm_deployment source and configuration. Response examples are illustrative excerpts, not responses captured from the test server. IDs, timestamps, PIDs, and available links depend on the record and deployment.

Example setup#

The examples use Bash and curl; examples that extract IDs or build JSON also use jq. Create a personal access token under Profile > Applications > Personal access tokens, and set:

B2INST_HOST='b2inst-test.gwdg.de'
ACCESS_TOKEN='<personal-access-token>'
RECORD_ID='<record-id>'
COMMUNITY_ID='<community-uuid>'

Replace IDs with values returned by your instance. Use the response’s id in record paths, not parent.id or a Handle URL. The test host above is the base for all examples on this page.

Requests that access drafts or change records require authentication and the relevant permissions. Public record reads and searches can omit the bearer header. Content-Type: application/json is for JSON request bodies; file bytes use application/octet-stream.

Record creation workflow#

The submission guide provides complete examples based on the soil moisture sensor in the release and migration correspondence.

  1. Create a draft. Put instrument fields inside metadata. Set files.enabled to false for metadata only, or true to enable uploads.
  2. If files are enabled, register, upload, and commit each file before publication.
  3. Choose the publication path permitted by your role and the community’s policies: direct community association and publication, or community review.

Adding a communities array to the creation body does not assign membership. Direct association uses POST /api/records/{id}/communities; the response’s parent.communities.ids lists accepted associations. In this deployment, ordinary users need a community before publication. A review receiver identifies the intended community but is not an accepted association yet.

B2INST record object#

Instrument field names are case-sensitive. metadata.Identifier is assigned by the service; omit it from creation requests. Other identifiers can be supplied in metadata.AlternateIdentifier. A complete metadata-only creation payload can look like this:

{
  "access": {"record": "public", "files": "public"},
  "files": {"enabled": false},
  "metadata": {
    "Name": "Soil moisture sensor 942 - HydraProbe Professional SDI-15",
    "SchemaVersion": "1.0",
    "InstrumentType": [
      {
        "instrumentTypeName": "Soil moisture and temperature sensor",
        "instrumentTypeIdentifier": {
          "instrumentTypeIdentifierType": "URL",
          "instrumentTypeIdentifierValue": "https://sms-cv.helmholtz.cloud/sms/cv/api/v1/equipmenttypes/599/"
        }
      }
    ],
    "Date": [{"Date": "2025-05-10", "dateType": "Commissioned"}],
    "Description": "Example device uploaded via API.",
    "LandingPage": "https://sms-cv.helmholtz.cloud/device/129",
    "Manufacturer": [{"manufacturerName": "Stevens Water Monitoring Systems"}],
    "Owner": [{"ownerName": "University of Rostock"}]
  }
}

Save this as record.json for the creation example. Include community extension fields when the selected community requires them.

access.record and access.files each accept public or restricted. The service computes access.status: a public record with files disabled and no active embargo has status metadata-only. Setting that status alone does not disable files.

An illustrative published response excerpt for a public record with an ePIC PID is:

{
  "id": "2qzkm-nzp30",
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records/2qzkm-nzp30",
    "self_html": "https://b2inst-test.gwdg.de/records/2qzkm-nzp30",
    "draft": "https://b2inst-test.gwdg.de/api/records/2qzkm-nzp30/draft",
    "versions": "https://b2inst-test.gwdg.de/api/records/2qzkm-nzp30/versions"
  },
  "parent": {
    "id": "4pd34-8t902",
    "communities": {
      "ids": ["e9b9792e-79fb-4b07-b6b4-b9c2bd06d095"],
      "default": "e9b9792e-79fb-4b07-b6b4-b9c2bd06d095"
    }
  },
  "versions": {"index": 1, "is_latest": true},
  "metadata": {
    "Name": "Soil moisture sensor 942 - HydraProbe Professional SDI-15",
    "Identifier": {
      "identifierType": "Handle",
      "identifierValue": "21.T11975/2qzkm-nzp30"
    }
  },
  "pids": {
    "epic": {
      "identifier": "http://hdl.handle.net/21.T11975/2qzkm-nzp30",
      "provider": "epic",
      "client": "epic"
    }
  },
  "access": {"record": "public", "files": "public", "status": "metadata-only"},
  "files": {"enabled": false, "count": 0, "total_bytes": 0, "entries": {}},
  "is_published": true,
  "is_draft": false,
  "status": "published"
}

metadata.Identifier.identifierValue is the bare Handle, while pids.epic.identifier is its resolver URL. The prefix depends on deployment configuration; 21.T11975 is the example prefix from the correspondence. The metadata identifier is assigned independently of external Handle registration, so its presence alone does not prove that the Handle resolves. New drafts can have no pids.epic yet. Registration on publication also depends on configuration and access restrictions; contact the service team if an expected ePIC PID is missing. Other entries, such as pids.oai, can appear.

List all records#

List published records visible to the caller. Results are paginated, and by default only the latest version of each record is returned.

HTTP method: GET
URL path: /api/records
Optional parameters: q, page, size, sort, allversions, configured facets
Status code on success: 200
Returns: A search result with hits.hits, hits.total, aggregations, sortBy, and pagination links.
curl --fail-with-body -sS --get "https://$B2INST_HOST/api/records" \
  --data-urlencode 'page=1' \
  --data-urlencode 'size=10' \
  --data-urlencode 'sort=newest'

Illustrative response excerpt for a search with one match; each hit normally contains more record fields:

{
  "hits": {
    "hits": [
      {
        "id": "2qzkm-nzp30",
        "metadata": {
          "Name": "Soil moisture sensor 942 - HydraProbe Professional SDI-15"
        },
        "links": {
          "self": "https://b2inst-test.gwdg.de/api/records/2qzkm-nzp30"
        }
      }
    ],
    "total": 1
  },
  "sortBy": "newest",
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records?page=1&size=10&sort=newest"
  }
}

Use the returned pagination links when available. hits.hits is the current page, while hits.total is the matching count. Use allversions=true to include older published versions.

List records per community#

HTTP method: GET
URL path: /api/communities/<community-id>/records
Optional parameters: q, page, size, sort, configured facets
Status code on success: 200
Returns: A paginated search result for records in the community.

Obtain the community UUID from GET /api/communities/<slug>; for example, GET /api/communities/eudat. Community URLs also accept the slug. The caller must be allowed to view the community and records.

curl --fail-with-body -sS --get "https://$B2INST_HOST/api/communities/$COMMUNITY_ID/records" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode 'page=1' \
  --data-urlencode 'size=10' \
  --data-urlencode 'sort=newest'

The result uses the same hits.hits / hits.total structure as List all records, with pagination links for the community endpoint.

Search records#

HTTP method: GET
URL path: /api/records
Optional parameters: q, page, size, sort, allversions, configured facets
Status code on success: 200
Returns: A paginated search result for matching published records visible to the caller.

q supports fielded searches such as metadata.Name:sensor. Use --get --data-urlencode so spaces, quotes, and other query characters are encoded correctly. Omitting q lists records without a text query. Page numbers start at 1.

QUERY_STRING='metadata.Name:"soil moisture"'
curl --fail-with-body -sS --get "https://$B2INST_HOST/api/records" \
  --data-urlencode "q=$QUERY_STRING" \
  --data-urlencode 'page=1' \
  --data-urlencode 'size=10' \
  --data-urlencode 'sort=bestmatch'

The configured published-record sort choices are bestmatch, newest, oldest, version, mostviewed, and mostdownloaded. Search defaults to bestmatch with a query and newest without one.

The deployment adds instrument facets instrument_type, manufacturer, measured_variable, and owner, alongside access_status, file_type, and the inherited resource_types facet. Prefer instrument facets for B2INST metadata; inherited resource-type buckets may be empty for instrument records. Facet values must match indexed values. For example:

curl --fail-with-body -sS --get "https://$B2INST_HOST/api/records" \
  --data-urlencode 'instrument_type=Soil moisture and temperature sensor' \
  --data-urlencode 'manufacturer=Stevens Water Monitoring Systems' \
  --data-urlencode 'access_status=metadata-only' \
  --data-urlencode 'sort=newest'

The response’s aggregations contains the configured facets and available buckets. file_type filters file extensions (for example, pdf); metadata-only records have no file types. Access-status values are open, embargoed, restricted, and metadata-only.

Search drafts#

Search your accessible uploads. Without the publication filter, this endpoint can return drafts and published records; it is not limited to never-published drafts.

HTTP method: GET
URL path: /api/user/records
Optional parameters: q, page, size, sort, shared_with_me, is_published, configured draft facets
Status code on success: 200
Returns: A paginated search result for uploads visible to the authenticated caller.

Use shared_with_me=false for your own uploads and shared_with_me=true for uploads shared with you by access grants. is_published=false selects unpublished records; omit it when looking for edits of already published records as well. These filters are optional, and authorization still applies.

curl --fail-with-body -sS --get "https://$B2INST_HOST/api/user/records" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode 'shared_with_me=false' \
  --data-urlencode 'is_published=false' \
  --data-urlencode 'page=1' \
  --data-urlencode 'size=10' \
  --data-urlencode 'sort=updated-desc'

The upload search supports bestmatch, updated-desc, updated-asc, newest, oldest, and version. Its configured facets are access_status, is_published, resource_types, and file_type.

Get specific record#

HTTP method: GET
URL path: /api/records/<record-id>
Status code on success: 200
Returns: The published record.
curl --fail-with-body -sS "https://$B2INST_HOST/api/records/$RECORD_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Authentication can be omitted for public records. Restricted records require permission to read them. Use the draft endpoint to retrieve an unpublished draft or an editing draft.

Get draft record#

HTTP method: GET
URL path: /api/records/<record-id>/draft
Status code on success: 200
Returns: The draft record.
curl --fail-with-body -sS "https://$B2INST_HOST/api/records/$RECORD_ID/draft" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The caller must have permission to read the draft. To prepare an editing draft for a published record, see Update published record metadata.

Record review process#

The record review process submits a draft’s first publication to a community. Set the receiver with PUT /api/records/<record-id>/draft/review before calling submit-review; putting the receiver in the submit request does not configure it. Use the community UUID returned by the Communities API for receiver.community.

These examples require a token with permission to manage the draft. Reading the review also requires permission to read the draft and its request. Fresh community-submission reviews cannot be created for edits of published records or new versions. Use the editing or versioning workflow for those records. An existing review that is not closed blocks direct publication; complete the review flow or remove an unsubmitted review before switching paths.

Get record review request#

GET /api/records/<record-id>/draft/review returns HTTP 200 with the review request. A review must already exist.

curl --fail-with-body -sS \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/review"

Illustrative response excerpt for an unsubmitted review (identifiers are placeholders):

{
  "id": "request-id",
  "type": "community-submission",
  "status": "created",
  "is_closed": false,
  "is_open": false,
  "receiver": {
    "community": "community-uuid"
  },
  "topic": {
    "record": "record-id"
  },
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/requests/request-id"
  }
}

Create and update a record review request#

PUT /api/records/<record-id>/draft/review creates or replaces the review and returns HTTP 200 with the request. An open review cannot be replaced through this endpoint.

Save this body as review.json, replacing community-uuid with the community UUID:

{
  "receiver": {
    "community": "community-uuid"
  },
  "type": "community-submission"
}
curl --fail-with-body -sS -X PUT \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @review.json \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/review"

The response has the review-request structure illustrated above. Alternatively, supply parent.review when first creating a draft; see the creation-time review example.

Delete a record review request#

DELETE /api/records/<record-id>/draft/review returns HTTP 204 with no response body. It removes the review association from the draft. An unsubmitted request is deleted; a closed request is retained in request history. An open review cannot be deleted, and this action is unavailable for an edit draft of an already published record.

curl --fail-with-body -sS -X DELETE \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/review"

Submit a record to review#

POST /api/records/<record-id>/draft/actions/submit-review returns HTTP 202 with the review request. The receiver must already be configured, the draft must pass publication validation, and the community must allow the user to submit records.

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '{}' \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/actions/submit-review"

With {}, submission may be accepted and published automatically when the community policies and the user’s permissions allow it. To require a review instead of attempting automatic acceptance, send {"require_review": true}. Inspect the returned request’s status: HTTP 202 alone does not mean the record is published. See the complete review example.

Record administration#

The following requests require authentication and the corresponding record permissions. A personal access token acts with its user’s permissions; it does not grant access to other users’ drafts automatically.

Create draft record#

POST /api/records returns HTTP 201 with the newly created draft, its id, and its API links.

Save the creation payload above as record.json, then create the draft:

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @record.json \
  "https://$B2INST_HOST/api/records" > draft.json
RECORD_ID=$(jq -er '.id' draft.json)
jq '{id, status, errors, metadata}' draft.json

A draft may be saved while incomplete. Before publication, supply a nonblank metadata.Name, a valid metadata.LandingPage, at least one Manufacturer with a nonblank manufacturerName, and at least one Owner with a nonblank ownerName. The service assigns Identifier. SchemaVersion defaults to "1.0", the supported value. Optional fields, when supplied, must also pass validation. See the complete creation example.

Common errors#

Draft creation and updates can return HTTP 201 or 200 with an errors array alongside the saved draft. Inspect that array and the returned metadata before publishing: invalid fields may not have been saved. Publication performs strict validation. A rejected validation request can return HTTP 400; this is an illustrative response excerpt:

{
  "message": "Validation error.",
  "status": 400
}

Check the response’s field-level errors for missing or invalid values. Unknown top-level or metadata fields can be ignored by the schema, so success alone does not establish that every supplied field was stored. See validation and common errors.

Update draft record metadata#

PUT /api/records/<record-id>/draft returns HTTP 200 with the updated draft. It replaces the metadata object; sending only metadata.Name can drop other metadata fields. The draft endpoint does not support PATCH or JSON Patch.

Retrieve the current draft, preserve its complete metadata and writable settings, and change the desired value. This example changes Description and leaves the draft’s PIDs unchanged by omitting pids:

curl --fail-with-body -sS \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft" > edit-draft.json

jq '{
  metadata,
  custom_fields,
  access: (.access | del(.status)),
  files: (.files | {enabled, default_preview, order})
} | .metadata.Description = "Updated device description after calibration."' \
  edit-draft.json > update.json

curl --fail-with-body -sS -X PUT \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @update.json \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft" > updated-draft.json
jq '{id, status, errors}' updated-draft.json

Check the returned errors even when the HTTP status is 200. Do not send response fields such as id, links, versions or is_published as instructions to change a record. Updating a draft does not publish it.

Reserve a DOI for a draft#

DOI reservation is not enabled by the checked deployment configuration: DATACITE_ENABLED = False. B2INST uses its configured ePIC Handle provider. Normal publication creates the required PID when applicable; no DOI-reservation call belongs in this deployment’s publication workflow.

The generic code defines POST /api/records/<record-id>/draft/pids/doi, but it requires an enabled DOI provider. On a deployment where DOI support is enabled, a successful reservation returns HTTP 201 with the draft record. This is a conditional API capability, not an available example for the checked configuration.

Delete a DOI from a draft#

DOI support is disabled in the checked configuration, as described above. The generic discard route is DELETE /api/records/<record-id>/draft/pids/doi; it also requires an enabled DOI provider and a PID that can be discarded. On a deployment where that operation is available, success returns HTTP 200 with the updated draft record.

Submit draft record for publication#

POST /api/records/<record-id>/draft/actions/publish returns HTTP 202 with the published record. Use this endpoint for direct publication when authorized by the record and community policies. Community review is a separate path using submit-review.

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/actions/publish" > published.json
jq '{id, status, is_published, pids}' published.json

The checked deployment requires the record to belong to at least one community for ordinary users to publish. First associate the draft with an allowed community or use community review, as described in the submission guide. With files.enabled: true, publication requires at least one file and all file transfers must be completed. For a record without uploaded files, use files.enabled: false. Existing draft file entries must be removed before disabling files.

Update published record metadata#

To amend published metadata without minting a new Handle, open an edit draft for the same published record, update that draft, and republish. This retains the record ID, version, and existing registered ePIC Handle when pids is left unchanged.

Set RECORD_ID to the published record’s ID, then open its edit draft:

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft" > edit-draft.json
jq '{id, status, pids}' edit-draft.json

POST /api/records/<record-id>/draft returns HTTP 201 with the edit draft, including when that draft already exists. Send this POST without metadata; it opens the draft and does not apply metadata from a request body. Then follow Update draft record metadata and Submit draft record for publication. Existing file content is preserved and locked in this workflow.

Omit pids from the update. On republication, the service preserves the published record’s existing required ePIC PID, including when that PID is absent from the edit draft’s response. If the record had no registered ePIC PID before editing, publication can create a missing PID according to the instance configuration. See the complete same-version update example, including how to compare the published Handle before and after editing.

To create a separate version with a new record ID, use POST /api/records/<record-id>/versions.

Report a record as an abuse record#

POST /api/records/<record-id>/report-abuse requires authentication and permission to read the record. It returns HTTP 201 with {"status": "report submitted"} after submitting the report notification. Anonymous reporting is not supported by this implementation.

All ten fields below must be present and nonempty; otherwise the endpoint returns HTTP 400. The UI offers Abuse or Inappropriate content, Copyrighted material, Not research data, and Illegal content as abuseType values. The API receives one string, not a set of booleans.

Save the following structure as report.json, replacing the illustrative details with the reporter’s actual details and reason:

{
  "abuseType": "Copyrighted material",
  "name": "Example reporter",
  "email": "reporter@example.org",
  "affiliation": "Example research institute",
  "phone": "+49 000 000000",
  "address": "Example Street 1",
  "city": "Example city",
  "zip": "12345",
  "country": "Germany",
  "message": "Please review the rights to the material in this record."
}
curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data-binary @report.json \
  "https://$B2INST_HOST/api/records/$RECORD_ID/report-abuse"

Success response:

{
  "status": "report submitted"
}

Delete draft record#

DELETE /api/records/<record-id>/draft returns HTTP 204 with no response body. Deleting an edit draft discards its unpublished changes and preserves the published record. Deleting a draft for a new record or a new version removes that unpublished draft. A draft with an open review cannot be deleted; cancel the review first.

curl --fail-with-body -sS -X DELETE \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft"

Record files#

Draft file operations require a token with access to the draft. Uploads also require files.enabled: true and permission to modify its files. For ordinary record owners, files copied into an editing draft of a published record are locked; create a new version when changing published files.

The examples use article.pdf as the file key. Use the exact same key throughout registration, upload and commit; URL-encode keys containing spaces or other reserved characters. Response excerpts are illustrative and omit generated timestamps, UUIDs and other fields.

Start file upload to a draft#

Register one or more file keys before uploading their binary content.

  HTTP method: POST

  URL path: /api/records/<record-id>/draft/files

  Required parameters: record-id

  Status code on success: 201

  Returns: The draft's file entries, including the registered entries

Command:

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  --data '[{"key":"article.pdf"},{"key":"measurements.csv"}]' \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files"

Response excerpt (one registered entry shown):

{
  "enabled": true,
  "entries": [
    {
      "key": "article.pdf",
      "status": "pending",
      "transfer": {"type": "L"},
      "links": {
        "content": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf/content",
        "self": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf",
        "commit": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf/commit"
      }
    }
  ],
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files"
  }
}

Upload file into draft record#

After registering a key, upload its bytes to the file entry’s links.content URL. Upload and commit are separate requests. See the complete file upload example.

  HTTP method: PUT

  URL path: /api/records/<record-id>/draft/files/<filename>/content

  Required parameters: record-id, filename

  Payload: The file's binary content

  Status code on success: 200

  Returns: The file entry

Command:

curl --fail-with-body -sS -X PUT \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/octet-stream' \
  --data-binary @article.pdf \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files/article.pdf/content"

Commit a file to a draft#

Commit after uploading the bytes. This finalizes the file entry and changes its status to completed.

  HTTP method: POST

  URL path: /api/records/<record-id>/draft/files/<filename>/commit

  Required parameters: record-id, filename

  Status code on success: 200

  Returns: The committed file entry
curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files/article.pdf/commit"

For multiple files, register all their keys in one request or register them individually. Upload and commit each key separately. A commit request does not upload binary content or publish the draft.

List draft’s files#

  HTTP method: GET

  URL path: /api/records/<record-id>/draft/files

  Required parameters: record-id

  Status code on success: 200

  Returns: Files in the draft record
curl --fail-with-body -sS -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files"

Response excerpt (after committing article.pdf):

{
  "enabled": true,
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files"
  },
  "entries": [
    {
      "key": "article.pdf",
      "mimetype": "application/pdf",
      "transfer": {"type": "L"},
      "status": "completed",
      "links": {
        "self": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf",
        "content": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf/content",
        "commit": "https://b2inst-test.gwdg.de/api/records/record-id/draft/files/article.pdf/commit"
      }
    }
  ]
}

When files are disabled, the response contains enabled: false and links, and omits entries, default_preview and order.

Get draft’s file metadata#

  HTTP method: GET

  URL path: /api/records/<record-id>/draft/files/<filename>

  Required parameters: record-id, filename

  Status code on success: 200

  Returns: Metadata of the specified file
curl --fail-with-body -sS -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files/article.pdf"

The response is a single file entry, with the same structure as an entry in the draft’s file listing. It does not return the file’s binary content.

Delete file from draft record#

  HTTP method: DELETE

  URL path: /api/records/<record-id>/draft/files/<filename>

  Required parameters: record-id, filename

  Status code on success: 204

  Returns: No content
curl --fail-with-body -sS -X DELETE \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/draft/files/article.pdf"

List files of record#

List the files of a published record. Public files can be read without authentication; restricted files require appropriate access.

  HTTP method: GET

  URL path: /api/records/<record-id>/files

  Required parameters: record-id

  Status code on success: 200

  Returns: Information about the files of the published record
curl --fail-with-body -sS -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/files"

Response excerpt:

{
  "enabled": true,
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records/record-id/files"
  },
  "entries": [
    {
      "key": "article.pdf",
      "mimetype": "application/pdf",
      "transfer": {"type": "L"},
      "status": "completed",
      "links": {
        "self": "https://b2inst-test.gwdg.de/api/records/record-id/files/article.pdf",
        "content": "https://b2inst-test.gwdg.de/api/records/record-id/files/article.pdf/content"
      }
    }
  ]
}

Published file entries have no commit link. Download a file with GET on its links.content URL.

Add externally referenced files to draft record#

The checked deployment configuration does not enable remote file references for ordinary API users. Its upload permissions allow local and multipart transfers, and no remote-domain allowlist is configured. Upload the file’s bytes using the registration, upload and commit workflow above. A remote URL in the registration payload is not a supported substitute in this configuration.

Record versioning#

Use a new version when you need a separate record version, including changes to its files. For metadata corrections to the current version, use the same-ID editing draft.

Create a new version of an existing published record into a new draft.#

  HTTP method: POST

  URL path: /api/records/<record-id>/versions

  Required parameters: record-id

  Status code on success: 201

  Returns: The next-version draft, with a new record ID and links, under the same parent

Send this POST without metadata. The endpoint copies metadata from the latest published version; a request body does not apply changes. If an open next-version draft already exists, it returns that draft with HTTP 201 instead of creating another one. You must start from a published record and have permission to create its next version.

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/versions" \
  -o next-version.json

NEW_RECORD_ID=$(jq -er '.id' next-version.json)

Use the returned draft ID (NEW_RECORD_ID) for subsequent PUT /api/records/<new-id>/draft, file operations and publication. The draft receives its own system-assigned metadata.Identifier; inspect the response instead of reusing the original version’s ID or Handle.

Files are not copied automatically, although files.enabled is retained. Upload new files, import the previous version’s files, or set files.enabled: false for a metadata-only version. See the complete new-version example.

  HTTP method: POST

  URL path: /api/records/<record-id>/draft/actions/files-import

  Required parameters: record-id

  Status code on success: 201

  Returns: The draft's imported file entries

Call this endpoint with the new draft ID to import files from the latest published version. The draft must have files enabled and no existing file entries. This requires permission to create files in the draft and read the source files. Importing the files does not publish the draft.

curl --fail-with-body -sS -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$NEW_RECORD_ID/draft/actions/files-import"

Get all record versions#

Use a record’s ID, or follow its links.versions URL. The service finds the shared parent internally and returns published versions visible to the caller. A record with only its first published version returns that version; it does not produce an empty result merely because no second version exists. Unpublished drafts are not included.

  HTTP method: GET

  URL path: /api/records/<record-id>/versions

  Required parameters: record-id

  Status code on success: 200

  Returns: A paginated search result containing the published record versions

Command:

curl --fail-with-body -sS -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/versions?page=1&size=25&sort=version"

Response excerpt (one published version):

{
  "hits": {
    "hits": [
      {
        "id": "record-id",
        "versions": {"index": 1, "is_latest": true},
        "links": {
          "self": "https://b2inst-test.gwdg.de/api/records/record-id"
        }
      }
    ],
    "total": 1
  },
  "sortBy": "version",
  "links": {
    "self": "https://b2inst-test.gwdg.de/api/records/record-id/versions?page=1&size=25&sort=version"
  }
}

Get latest version of a record#

  HTTP method: GET

  URL path: /api/records/<record-id>/versions/latest

  Required parameters: record-id

  Status code on success: 301

  Returns: A redirect to the latest published record's API URL

The Location response header points to the latest published version. Use curl --fail-with-body -sS -L to follow the redirect and receive that record’s JSON response (HTTP 200, provided you can read it).

curl --fail-with-body -sS -L -H "Authorization: Bearer $ACCESS_TOKEN" \
  "https://$B2INST_HOST/api/records/$RECORD_ID/versions/latest"

Access#

Access links grant access to the record family, including its versions. Creating, reading, updating, listing, and deleting these links requires permission to manage the record (for example, its owner). The examples use the authenticated ACCESS_TOKEN and RECORD_ID configured above.

POST /api/records/{record-id}/access/links returns 201 Created with the link object.

Use exactly one permission: view for published content, preview to include drafts, or edit to allow editing. The literal string view/preview/edit is invalid. description and expires_at are optional. An expiration must be a future date in YYYY-MM-DD format; omitting it creates a link without an expiration.

curl --fail-with-body --silent --show-error \
  --request POST "https://$B2INST_HOST/api/records/$RECORD_ID/access/links" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"permission":"view","description":"Instrument collaborators"}' \
  --output access-link.json

LINK_ID=$(jq -er '.id' access-link.json)

Illustrative response excerpt:

{
  "id": "716ab90b-f7a3-4136-a1f5-07f1a8a2d848",
  "permission": "view",
  "description": "Instrument collaborators"
}

The full response also includes created_at and the generated token; expires_at is included when an expiration is set. A recipient uses that token as the token query parameter on the record or file URL, for example https://b2inst-test.gwdg.de/api/records/{record-id}?token={token}.

GET /api/records/{record-id}/access/links/{link-id} returns 200 OK with the same link object format as creation.

curl --fail-with-body --silent --show-error \
  "https://$B2INST_HOST/api/records/$RECORD_ID/access/links/$LINK_ID" \
  --header "Authorization: Bearer $ACCESS_TOKEN"

PATCH /api/records/{record-id}/access/links/{link-id} returns 200 OK with the updated link object. Send only the fields to change.

curl --fail-with-body --silent --show-error \
  --request PATCH "https://$B2INST_HOST/api/records/$RECORD_ID/access/links/$LINK_ID" \
  --header "Authorization: Bearer $ACCESS_TOKEN" \
  --header 'Content-Type: application/json' \
  --data '{"description":"Updated collaborator access"}'

You can change permission, description, or expires_at. You can introduce an expiration or shorten an existing one, but you cannot postpone or remove an existing expiration. Create a new link if you need a later expiration. PUT is not supported and returns 405 Method Not Allowed.

DELETE /api/records/{record-id}/access/links/{link-id} returns 204 No Content and revokes the link.

curl --fail-with-body --silent --show-error \
  --request DELETE "https://$B2INST_HOST/api/records/$RECORD_ID/access/links/$LINK_ID" \
  --header "Authorization: Bearer $ACCESS_TOKEN"

GET /api/records/{record-id}/access/links returns 200 OK with a JSON list wrapper. Each member of hits.hits has the same format as the link creation response; hits.total is an integer.

curl --fail-with-body --silent --show-error \
  "https://$B2INST_HOST/api/records/$RECORD_ID/access/links" \
  --header "Authorization: Bearer $ACCESS_TOKEN"

Example response when no links remain:

{
  "hits": {
    "hits": [],
    "total": 0
  }
}

Send record access request#

POST /api/records/{record-id}/access/request returns 200 OK with JSON. This requests access to restricted files; it does not immediately grant access. For an authenticated request, the caller must be able to read the published record and must not already have access to its files.

Use the requesting user’s token and set REQUESTER_EMAIL to the email address of that same account. The service uses this email match to select the authenticated request workflow.

REQUESTER_EMAIL='your-account-email@example.org'

jq -n --arg email "$REQUESTER_EMAIL" \
  '{email: $email, message: "Please grant access to the instrument files."}' |
  curl --fail-with-body --silent --show-error \
    --request POST "https://$B2INST_HOST/api/records/$RECORD_ID/access/request" \
    --header "Authorization: Bearer $ACCESS_TOKEN" \
    --header 'Content-Type: application/json' \
    --data-binary @-

Illustrative authenticated response excerpt:

{
  "id": "d0920676-124f-4937-a145-a1860157108a",
  "type": "user-access-request",
  "status": "submitted"
}

The record owner must accept the request before file access is granted.

For the guest workflow, omit the authorization header and provide email, full_name, and consent_to_share_personal_data, with an optional message. The consent field is the string "true" or "false", not a JSON boolean. The service first emails a verification link and responds with a message; the request is created after email verification. Open the guest verification link while signed out. A submitted email that differs from the authenticated user’s email also selects this guest workflow.

Example guest request body:

{
  "email": "researcher@example.org",
  "full_name": "Example Researcher",
  "message": "Please grant access to the instrument files.",
  "consent_to_share_personal_data": "true"
}

Example guest response:

{
  "message": "Verification link sent out, please check your e-mail inbox"
}

Last update : 22.09.2026

Last review : 22.09.2026