Skip to content

API Guide

The de.NBI Service Registry REST API allows programmatic access to service registrations.

Interactive documentation is available at:

  • Swagger UI: /api/docs/
  • ReDoc: /api/redoc/
  • OpenAPI schema (JSON): /api/schema/

Both Swagger UI (swagger-ui-dist 5.18.2) and ReDoc (2.2.0) assets are vendored locally in static/ — no CDN or external requests are made when loading the docs pages.


Authentication

Two authentication schemes are supported.

Admin API Key (scoped, independent of user accounts)

Authorization: AdminKey <key>

Machine-to-machine keys that are independent of any staff user account and carry an explicit scope. Create as many as needed — one per consumer, rotate individually.

Scope HTTP methods allowed Typical use case
read GET / HEAD / OPTIONS only Public-facing website, dashboard, read-only CI
full All methods (GET/POST/PATCH/DELETE) Trusted back-end integration

Why use a read scope key?

If you give a read key to a third-party website that renders registry data, leaking the key is low-risk: the worst-case outcome is that someone reads data that might already be public. They cannot modify submissions, create or delete reference data, or access any field that the serialisers already exclude (internal_contact_email, submission_ip, etc.).

Creating a key (see Admin Guide → Admin API Keys):

  1. Log in to /admin-denbi/
  2. Go to API → Admin API Keys → Add Admin API Key
  3. Enter a label (e.g. Public website) and choose the scope
  4. Save — the full plaintext key appears once. Copy it now.

Using it:

# Read-only access — safe to embed in a public application
curl https://service-registry.bi.denbi.de/api/v1/submissions/ \
  -H "Authorization: AdminKey <your-key>"

# Attempt to write with a read-scope key → 403 Forbidden
curl -X POST https://service-registry.bi.denbi.de/api/v1/categories/ \
  -H "Authorization: AdminKey <read-key>" \
  -H "Content-Type: application/json" \
  -d '{"name": "New"}'
# → {"detail": "This key is read-only. Use a full-access Admin API Key to modify data."}

Revoking a key:

Set Is active to unchecked in the admin UI. The record is retained for audit purposes; the key immediately stops authenticating.


Submission API Key

Authorization: ApiKey <key>

Issued when a service is registered (via the web form or POST /api/v1/submissions/). Scoped to a single submission. The plaintext key is shown once — store it securely.

Two scopes are available (set by admins via the API Key admin):

Scope REST API Web edit form (/update/edit/)
read GET only View form (GET), cannot submit
write GET + PATCH View and submit changes

Scope is enforced consistently in both the REST API and the web edit form. A read key stored in the update session can load the pre-populated form for inspection but any POST attempt is rejected and redirected to the key-entry page.

Using it:

curl https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>"

Endpoints

Register a service

POST /api/v1/submissions/ — no authentication required. Submits a new service registration.

Response (201):

{
  "id": "26a59fcb-...",
  "service_name": "MyTool",
  "api_key": "oGzQk9...",
  "api_key_warning": "This key is shown ONCE. Store it securely.",
  "status": "submitted",
  ...
}

List all submissions

GET /api/v1/submissions/ — requires admin API Key. Returns paginated full detail for all submissions.

Query parameters:

Parameter Example Description
status ?status=approved Filter by status (draft, submitted, under_review, approved, rejected, deprecated)
service_center ?service_center=BioinfoProt Filter by centre short name
year_established ?year_established=2021 Filter by year
register_as_elixir ?register_as_elixir=true Filter by ELIXIR flag
ordering ?ordering=-submitted_at Sort (prefix - for descending)

Response (200):

{
  "count": 42,
  "next": "http://.../api/v1/submissions/?page=2",
  "previous": null,
  "results": [
    {
      "id": "...",
      "service_name": "...",
      "status": "approved",
      "edam_topics": [{"uri": "...", "accession": "topic_0091", "label": "Proteomics"}],
      "edam_operations": [...],
      "responsible_pis": [{"last_name": "...", "orcid": "..."}],
      "biotoolsrecord": {
        "id": "...",
        "biotools_id": "my-tool",
        "biotools_url": "https://bio.tools/my-tool",
        "name": "My Tool",
        "description": "A tool for ...",
        "homepage": "https://example.com/my-tool",
        "version": ["1.0", "1.1"],
        "license": "MIT",
        "maturity": "Mature",
        "cost": "Free of charge",
        "tool_type": ["Web application", "Command-line tool"],
        "operating_system": ["Linux", "Mac"],
        "edam_topic_uris": ["http://edamontology.org/topic_0091"],
        "edam_topics_resolved": [
          {"uri": "http://edamontology.org/topic_0091", "accession": "topic_0091", "label": "Proteomics"}
        ],
        "functions": [
          {
            "position": 0,
            "operations": [{"uri": "http://edamontology.org/operation_0004", "term": "Operation"}],
            "inputs": [{"data": {"uri": "...", "term": "Sequence"}, "formats": []}],
            "outputs": [{"data": {"uri": "...", "term": "Report"}, "formats": []}],
            "cmd": null,
            "note": null
          }
        ],
        "publications": [{"doi": "10.1000/xyz", "pmid": null, "pmcid": null, "type": "Primary", "note": null}],
        "documentation": [{"url": "https://example.com/docs", "type": "General", "note": null}],
        "download": [],
        "links": [],
        "last_synced_at": "2024-03-15T10:30:00Z",
        "sync_error": null
      },
      ...
    }
  ]
}

Retrieve a submission

GET /api/v1/submissions/{id}/ — requires ApiKey. Returns your own submission in full detail. Returns 403 if the key does not belong to this submission.


Update a submission

PATCH /api/v1/submissions/{id}/ — requires ApiKey with write scope. Partial update — include only changed fields.

Updating an approved submission resets its status to submitted for re-review.

curl -X PATCH https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>" \
  -H "Content-Type: application/json" \
  -d '{"kpi_start_year": "2026"}'

Full PUT is not supported — use PATCH.

Email notifications on PATCH

Every successful PATCH triggers the same notification flow as a submitter web-form edit: an admin email with the full submission report, a field-level what changed diff table, and a direct link to the admin change view. If any fields actually changed, the submitter also receives a separate confirmation email with the same diff table. No notification is sent when the request body contains no actual changes.


Custom permissions

Two semantic permissions are defined on ServiceSubmission beyond the standard CRUD permissions:

Codename What it gates API endpoint impact
submissions.approve_servicesubmission Approve and reject status transitions; bulk approve/reject actions. Admin-only via /admin/. Not exposed via API.
submissions.manage_apikeys Issue, reset, and revoke SubmissionAPIKey objects. Admin-only via /admin/. Not exposed via API.

These permissions are enforced in the Django admin backend (see Admin Guide → Custom permission codenames). They are kept separate from standard CRUD permissions so editors can fix data without having final-decision authority or being able to create credentials that grant submitters write access.


Service logos can be attached to a submission at registration time or added/replaced later via PATCH.

On POST (new registration):

curl -X POST https://service-registry.bi.denbi.de/api/v1/submissions/ \
  -H "Authorization: ApiKey <your-key>" \
  -F "service_name=MyTool" \
  -F "logo=@service-logo.png"

On PATCH (update existing submission):

curl -X PATCH https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>" \
  -F "logo=@service-logo.svg"

Use multipart/form-data for logo uploads

When uploading a logo, send the request as multipart/form-data (-F flags in curl) instead of application/json. You can mix file and text fields in the same request. JSON-only requests (Content-Type: application/json) cannot carry file data.

Logo field behaviour:

Field Direction Type Notes
logo write-only file Accepted in multipart/form-data requests only. Omit to leave the existing logo unchanged.
logo_url read-only string | null Absolute URL to the stored logo file, or null if no logo has been uploaded. Returned in all submission responses.

Accepted formats: PNG, JPEG, SVG — max 10 MB (configurable via logo_max_bytes in config/site.toml).

Security processing applied automatically:

  • Magic-byte type detection (file extension and MIME header are never trusted)
  • JPEG/PNG: re-encoded via Pillow to strip EXIF metadata and verify integrity
  • SVG: parsed with Python's stdlib XML parser (safe on Python 3.12+/Expat 2.7.1, which blocks XXE and entity-expansion attacks), then scrubbed of <script> elements, on* event-handler attributes, and non-fragment external URLs
  • Original filename is discarded; the file is stored under a UUID path (media/logos/<uuid4>.<ext>)

Old logos are not deleted when a logo is replaced — previous files remain on disk.


Reference data

All reference data endpoints require an admin API Key. All three resources support full CRUD and follow the same pattern.

Method URL pattern Description
GET /api/v1/categories/ List all categories (active + inactive)
POST /api/v1/categories/ Create a category
GET /api/v1/categories/{id}/ Retrieve a category
PATCH /api/v1/categories/{id}/ Partial update
PUT /api/v1/categories/{id}/ Full update
DELETE /api/v1/categories/{id}/ Soft-delete (sets is_active=False)

Same pattern applies for /api/v1/service-centers/{id}/ and /api/v1/pis/{id}/.

DELETE is a soft-delete — the record is retained in the database and remains linked to existing submissions. is_active=False hides it from the registration form but does not break any foreign keys.

Filter: ?is_active=true|false narrows the list to active or inactive records. Without the filter, both active and inactive records are returned.

Fields — /api/v1/categories/

Field Type Writable Notes
id integer no Auto-assigned
name string yes Must be unique
is_active boolean yes Defaults to true

Fields — /api/v1/service-centers/

Field Type Writable Notes
id UUID no Auto-assigned
short_name string yes e.g. "HD-HuB"
full_name string yes Full official name
website URL yes Optional
is_active boolean yes Defaults to true

Fields — /api/v1/pis/

Field Type Writable Notes
id UUID no Auto-assigned
last_name string yes Required
first_name string yes Required
display_name string no Computed ("Last, First")
email string yes Internal — never exposed in submission responses
institute string yes Optional
orcid string yes Optional; validated format + checksum
is_active boolean yes Defaults to true
is_associated_partner boolean yes Mark true for the generic "Associated partner" entry

curl examples

# List all service centres (active + inactive)
curl https://service-registry.bi.denbi.de/api/v1/service-centers/ \
  -H "Authorization: ApiKey <admin-key>"

# List active categories only
curl "https://service-registry.bi.denbi.de/api/v1/categories/?is_active=true" \
  -H "Authorization: ApiKey <admin-key>"

# Create a new PI
curl -X POST https://service-registry.bi.denbi.de/api/v1/pis/ \
  -H "Authorization: ApiKey <admin-key>" \
  -H "Content-Type: application/json" \
  -d '{"last_name": "Smith", "first_name": "Alice", "email": "a.smith@example.com", "institute": "Example University"}'

# Deactivate a service centre (soft-delete)
curl -X DELETE https://service-registry.bi.denbi.de/api/v1/service-centers/<id>/ \
  -H "Authorization: ApiKey <admin-key>"

# Re-activate a category via PATCH
curl -X PATCH https://service-registry.bi.denbi.de/api/v1/categories/<id>/ \
  -H "Authorization: ApiKey <admin-key>" \
  -H "Content-Type: application/json" \
  -d '{"is_active": true}'

bio.tools records

Bio.tools metadata is automatically fetched and kept in sync when a submission includes a biotools_id. The data is embedded directly in every submission response under the biotoolsrecord key (see above).

A standalone read-only endpoint is also available for admin API keys:

Method URL Description
GET /api/v1/biotools/ List all bio.tools records
GET /api/v1/biotools/{biotools_id}/ Retrieve one bio.tools record by its bio.tools ID

Authentication: admin API Key required.

Both endpoints return the same field set as the biotoolsrecord object shown in the submission response above, plus a submission link field pointing to the associated submission.

# List all synced bio.tools records
curl https://service-registry.bi.denbi.de/api/v1/biotools/ \
  -H "Authorization: AdminKey <admin-key>"

# Retrieve a specific record
curl https://service-registry.bi.denbi.de/api/v1/biotools/my-tool/ \
  -H "Authorization: AdminKey <admin-key>"

bio.tools sync actions

Admins can trigger a manual re-sync from the bio.tools record list in the Django admin (Bio.tools records → select records → "Sync selected records from bio.tools now"). Syncs are queued as Celery tasks and run in the background.


EDAM ontology terms

GET /api/v1/edam/ — public, no authentication required. Returns all non-obsolete EDAM terms.

Filter: ?branch=topic|operation|data|format, ?q=<search>

EDAM term detail

GET /api/v1/edam/{accession}/ — public. Look up by accession (e.g. topic_0091) or UUID.


Response shape

All error responses follow this envelope:

{
  "error": { "detail": "Authentication credentials were not provided." },
  "request_id": "8e26f6d5-0094-48ea-9a36-c417921815a9"
}

The request_id is included in every response (success and error) as X-Request-ID header and in error bodies. Use it when reporting issues.


Excluded fields

The following fields are never included in any API response regardless of authentication level:

  • internal_contact_email
  • internal_contact_name
  • submission_ip
  • user_agent_hash

curl examples

# Register a new service (public)
curl -X POST https://service-registry.bi.denbi.de/api/v1/submissions/ \
  -H "Content-Type: application/json" \
  -d @submission.json

# List all submissions (admin key)
curl https://service-registry.bi.denbi.de/api/v1/submissions/ \
  -H "Authorization: AdminKey <admin-key>"

# Retrieve your submission (ApiKey)
curl https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>"

# Update a field (ApiKey, write scope)
curl -X PATCH https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>" \
  -H "Content-Type: application/json" \
  -d '{"website_url": "https://new-url.example.com"}'

# Upload or replace a logo (ApiKey, write scope — multipart/form-data)
curl -X PATCH https://service-registry.bi.denbi.de/api/v1/submissions/<id>/ \
  -H "Authorization: ApiKey <your-key>" \
  -F "logo=@service-logo.png"

# Browse EDAM topics (public)
curl "https://service-registry.bi.denbi.de/api/v1/edam/?branch=topic&q=proteomics"