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):
- Log in to
/admin-denbi/ - Go to API → Admin API Keys → Add Admin API Key
- Enter a label (e.g.
Public website) and choose the scope - 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.
Upload or update a service logo¶
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_emailinternal_contact_namesubmission_ipuser_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"