Dashboard API
Authenticated API used by the internal Legaciti dashboard. Administrative endpoints are marked as internal where relevant.
Authenticated API used by the internal Legaciti dashboard. Administrative endpoints are marked as internal where relevant.
Version: 1.0.0
Raw OpenAPI JSON: /openapi/dashboard-api.json
Servers
Section titled “Servers”- https://dash.legaciti.org - Primary dashboard API domain
- https://my.legaciti.org - Alternate dashboard API domain
Endpoints
Section titled “Endpoints”Activity
Section titled “Activity”| Method | Path | Summary |
|---|---|---|
| GET | /api/activity | List workspace activity |
| POST | /api/activity/cleanup | Cleanup workspace activity |
GET /api/activity
Section titled “GET /api/activity”List workspace activity Returns a newest-first, workspace-scoped activity timeline with optional filters. Authentication: betterAuthSession Tags: Activity
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| from | query | integer | no | |
| to | query | integer | no | |
| event_type | query | string | no | |
| severity | query | string | no | |
| status | query | string | no | |
| actor | query | string | no | |
| q | query | string | yes | |
| page | query | integer | yes | |
| per_page | query | integer | yes | |
| cursor | query | string | no |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Paginated activity events.
Section titled “200 - Paginated activity events.”Content type: application/json
{ "type": "object", "properties": { "events": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "pages": { "type": "integer" }, "next_cursor": { "type": [ "string", "null" ] }, "has_more": { "type": "boolean" } }, "required": [ "events", "page", "per_page", "total", "pages", "next_cursor", "has_more" ]}400 - Invalid query or missing workspace context.
Section titled “400 - Invalid query or missing workspace context.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/activity/cleanup
Section titled “POST /api/activity/cleanup”Cleanup workspace activity Deletes activity rows for the active workspace. Allowed for workspace_admin and superadmin roles. Authentication: betterAuthSession Tags: Activity
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "mode": { "type": "string", "enum": [ "all", "before_date" ] }, "before_date": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "confirmation": { "type": "string", "minLength": 1, "maxLength": 120 } }, "required": [ "mode", "confirmation" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Cleanup result.
Section titled “200 - Cleanup result.”Content type: application/json
{ "type": "object", "properties": { "ok": { "type": "boolean" }, "mode": { "type": "string", "enum": [ "all", "before_date" ] }, "deleted_count": { "type": "integer" } }, "required": [ "ok", "mode", "deleted_count" ]}400 - Invalid request body or missing workspace context.
Section titled “400 - Invalid request body or missing workspace context.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin roles.
Section titled “403 - Forbidden for non-admin roles.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}Import
Section titled “Import”| Method | Path | Summary |
|---|---|---|
| POST | /api/import/orcid | Queue ORCID import |
| POST | /api/import/doi | Import DOI |
POST /api/import/orcid
Section titled “POST /api/import/orcid”Queue ORCID import Queues ORCID imports from the import surface. Similar to ingestion queueing but exposed under import routes. Authentication: betterAuthSession Tags: Import
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "orcid_ids": { "minItems": 1, "maxItems": 100, "type": "array", "items": { "type": "string", "minLength": 1 } }, "skip_existing": { "type": "boolean" }, "trigger_source": { "type": "string", "enum": [ "people_page" ] } }, "required": [ "orcid_ids" ], "additionalProperties": false}Responses
Section titled “Responses”202 - Queued import jobs.
Section titled “202 - Queued import jobs.”Content type: application/json
{ "type": "object", "properties": { "queued": { "type": "integer" }, "jobs": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "queued", "jobs" ]}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/import/doi
Section titled “POST /api/import/doi”Import DOI Fetches and upserts publication metadata directly from a DOI and optionally links people. Authentication: betterAuthSession Tags: Import
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "doi": { "type": "string", "minLength": 1 }, "person_orcids": { "default": [], "maxItems": 100, "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "doi", "person_orcids" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Existing publication refreshed.
Section titled “200 - Existing publication refreshed.”Content type: application/json
{ "type": "object", "additionalProperties": true}201 - New publication created from DOI import.
Section titled “201 - New publication created from DOI import.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid DOI import payload.
Section titled “400 - Invalid DOI import payload.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}| Method | Path | Summary |
|---|---|---|
| GET | /health | Health check |
| GET | /me | Current dashboard identity |
| GET | /debug-auth | Authentication debug payload |
GET /health
Section titled “GET /health”Health check Simple health endpoint for worker availability. Authentication: Public Tags: Info
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Worker health response.
Section titled “200 - Worker health response.”Content type: application/json
{ "type": "object", "properties": { "service": { "type": "string", "enum": [ "dashboard-api" ] }, "status": { "type": "string", "enum": [ "ok" ] } }, "required": [ "service", "status" ]}GET /me
Section titled “GET /me”Current dashboard identity Returns the authenticated user identity and workspace context when a valid Better Auth session is present. Authentication: betterAuthSession Tags: Info
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Authenticated email payload.
Section titled “200 - Authenticated email payload.”Content type: application/json
{ "type": "object", "properties": { "email": { "type": [ "string", "null" ] } }, "required": [ "email" ]}GET /debug-auth
Section titled “GET /debug-auth”Authentication debug payload Returns the resolved auth context (identity, source, superadmin flag, memberships) for the current Better Auth session. Internal: yes Authentication: Public Tags: Info
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Debug payload with identity and token-derived fields.
Section titled “200 - Debug payload with identity and token-derived fields.”Content type: application/json
{ "type": "object", "additionalProperties": true}Ingestion
Section titled “Ingestion”| Method | Path | Summary |
|---|---|---|
| POST | /api/ingest | Queue ORCID ingestion |
| GET | /api/ingest/status | Poll ingestion status |
| GET | /api/ingest/stuck | List stuck ingestion jobs |
| POST | /api/ingest/stuck/{jobId}/force-fail | Force-fail an ingestion job |
POST /api/ingest
Section titled “POST /api/ingest”Queue ORCID ingestion Queues one or more ORCID ingestion jobs and returns the job records that were created. Authentication: betterAuthSession Tags: Ingestion
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "orcid_ids": { "minItems": 1, "maxItems": 100, "type": "array", "items": { "type": "string", "minLength": 1 } }, "skip_existing": { "type": "boolean" }, "trigger_source": { "type": "string", "enum": [ "people_page" ] } }, "required": [ "orcid_ids" ], "additionalProperties": false}Responses
Section titled “Responses”202 - Queued ingestion jobs.
Section titled “202 - Queued ingestion jobs.”Content type: application/json
{ "type": "object", "properties": { "queued": { "type": "integer" }, "requested": { "type": "integer" }, "jobs": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "queued", "requested", "jobs" ]}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/ingest/status
Section titled “GET /api/ingest/status”Poll ingestion status Returns the most recent ingestion status for each requested ORCID identifier. Authentication: betterAuthSession Tags: Ingestion
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| orcid_ids | query | string | yes | Comma-separated ORCID identifiers. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Status payloads by ORCID.
Section titled “200 - Status payloads by ORCID.”Content type: application/json
{ "type": "object", "properties": { "statuses": { "type": "array", "items": { "type": "object", "additionalProperties": true } } }, "required": [ "statuses" ]}400 - Missing query parameter.
Section titled “400 - Missing query parameter.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/ingest/stuck
Section titled “GET /api/ingest/stuck”List stuck ingestion jobs Lists queued or processing ingestion jobs older than a configurable threshold. Internal: yes Authentication: betterAuthSession Tags: Ingestion
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| threshold_seconds | query | integer | no | Age threshold in seconds. |
| limit | query | integer | no | Maximum stuck jobs to return. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Stuck ingestion jobs across shards.
Section titled “200 - Stuck ingestion jobs across shards.”Content type: application/json
{ "type": "object", "additionalProperties": true}POST /api/ingest/stuck/{jobId}/force-fail
Section titled “POST /api/ingest/stuck/{jobId}/force-fail”Force-fail an ingestion job Marks a queued or processing ingestion job as failed for operational recovery. Internal: yes Authentication: betterAuthSession Tags: Ingestion
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| jobId | path | string | yes | Ingestion job identifier. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "orcid_id": { "type": "string" }, "error_message": { "type": "string" } }, "required": [ "orcid_id" ]}Responses
Section titled “Responses”200 - Forced failure result.
Section titled “200 - Forced failure result.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Job not found or not active.
Section titled “404 - Job not found or not active.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}People
Section titled “People”| Method | Path | Summary |
|---|---|---|
| GET | /api/people | List people |
| GET | /api/people/{orcid} | Get person |
| PATCH | /api/people/{orcid} | Update person profile |
| GET | /api/people/{orcid}/publications | List person publications |
| PATCH | /api/people/{orcid}/affiliation | Update person affiliation |
GET /api/people
Section titled “GET /api/people”List people Lists people with optional affiliation and search filters. Authentication: betterAuthSession Tags: People
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| page | query | integer | yes | 1-based page number. |
| perPage | query | integer | yes | Items per page. |
| q | query | string | yes | Search by person name or ORCID. |
| sort | query | string | yes | Sort field. |
| dir | query | string | yes | Sort direction. |
| entity_id | query | string | no | Entity filter such as cesam. |
| affiliation_status | query | string | no | Normalized affiliation status filter. |
| active_only | query | boolean | yes | Only include currently active memberships. |
| category_id | query | string | no | |
| locale | query | string | yes |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Paginated person list.
Section titled “200 - Paginated person list.”Content type: application/json
{ "type": "object", "properties": { "people": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "pages": { "type": "integer" } }, "required": [ "people", "page", "per_page", "total", "pages" ]}400 - Invalid query parameters.
Section titled “400 - Invalid query parameters.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/people/{orcid}
Section titled “GET /api/people/{orcid}”Get person Returns a single person including current normalized affiliation fields. Authentication: betterAuthSession Tags: People
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| orcid | path | string | yes | Person ORCID identifier. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Person payload.
Section titled “200 - Person payload.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Missing ORCID.
Section titled “400 - Missing ORCID.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Person not found.
Section titled “404 - Person not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}PATCH /api/people/{orcid}
Section titled “PATCH /api/people/{orcid}”Update person profile Updates the manually editable person name and biography fields. Authentication: betterAuthSession Tags: People
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| orcid | path | string | yes | Person ORCID identifier. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "name": { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "string", "minLength": 1 } } ] }, "biography": { "anyOf": [ { "anyOf": [ { "type": "string", "minLength": 1 }, { "type": "object", "propertyNames": { "type": "string" }, "additionalProperties": { "type": "string", "minLength": 1 } } ] }, { "type": "null" } ] }, "category_ids": { "maxItems": 50, "type": "array", "items": { "type": "string", "minLength": 1 } } }, "required": [ "name" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Profile update acknowledged.
Section titled “200 - Profile update acknowledged.”Content type: application/json
{ "type": "object", "properties": { "success": { "type": "boolean" } }, "required": [ "success" ]}400 - Invalid profile payload.
Section titled “400 - Invalid profile payload.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/people/{orcid}/publications
Section titled “GET /api/people/{orcid}/publications”List person publications Returns publications linked to a single person, with optional inclusion of soft-deleted rows. Authentication: betterAuthSession Tags: People
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| orcid | path | string | yes | Person ORCID identifier. |
| page | query | integer | yes | 1-based page number. |
| perPage | query | integer | yes | Items per page. |
| includeDeleted | query | boolean | yes | Include soft-deleted publications. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Paginated publication list for a single person.
Section titled “200 - Paginated publication list for a single person.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid query parameters.
Section titled “400 - Invalid query parameters.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}PATCH /api/people/{orcid}/affiliation
Section titled “PATCH /api/people/{orcid}/affiliation”Update person affiliation Closes the current active membership row and creates a new one for the given entity and status. Authentication: betterAuthSession Tags: People
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| orcid | path | string | yes | Person ORCID identifier. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "entity_id": { "type": "string", "minLength": 1 }, "status": { "type": "string", "enum": [ "cesam", "external" ] }, "starts_at": { "type": "integer", "exclusiveMinimum": 0, "maximum": 9007199254740991 } }, "required": [ "entity_id", "status" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Affiliation update payload.
Section titled “200 - Affiliation update payload.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid affiliation request.
Section titled “400 - Invalid affiliation request.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Person or entity not found.
Section titled “404 - Person or entity not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}Publications
Section titled “Publications”| Method | Path | Summary |
|---|---|---|
| GET | /api/publications | List publications |
| GET | /api/publications/stats | Publication totals |
| GET | /api/publications/stats/timeseries | Publication timeseries |
| GET | /api/publications/{id} | Get publication |
| PATCH | /api/publications/{id} | Patch publication |
| DELETE | /api/publications/{id} | Soft-delete publication |
| POST | /api/publications/bulk | Bulk publication action |
GET /api/publications
Section titled “GET /api/publications”List publications Lists dashboard publications across shards with filters for visibility, deletion state, ownership, and metadata fields. Authentication: betterAuthSession Tags: Publications
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| page | query | integer | yes | 1-based page number. |
| perPage | query | integer | yes | Items per page. |
| orcid_id | query | string | no | Filter to publications linked to a person ORCID. |
| q | query | string | yes | Search term applied to title and author text. |
| sort | query | string | yes | Sort field. |
| dir | query | string | yes | Sort direction. |
| visibility | query | string | yes | Visibility filter. |
| status | query | string | yes | Deletion-state filter. |
| publication_type | query | string | no | Exact publication type filter. |
| publication_year | query | integer | no | Publication year filter. |
| min_cited_by | query | integer | no | Minimum citation count filter. |
| locale | query | string | yes |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Paginated publication list.
Section titled “200 - Paginated publication list.”Content type: application/json
{ "type": "object", "properties": { "publications": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "string" }, "person_orcids": { "type": "array", "items": { "type": "string" } }, "person": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "title": { "type": [ "string", "null" ] }, "apa_citation": { "type": [ "string", "null" ] }, "doi_url": { "type": "string" }, "publication_date": { "type": [ "string", "null" ] }, "publication_year": { "type": [ "integer", "null" ] }, "publication_type": { "type": [ "string", "null" ] }, "cited_by_count": { "type": [ "integer", "null" ] }, "last_fetched_at": { "type": "integer" }, "edit_count": { "type": "integer" }, "visible": { "type": "boolean" }, "deleted_at": { "type": [ "string", "null" ] } }, "required": [ "id", "person_orcids", "person", "doi_url", "last_fetched_at", "edit_count", "visible" ], "additionalProperties": true } }, "page": { "type": "integer" }, "per_page": { "type": "integer" }, "total": { "type": "integer" }, "pages": { "type": "integer" }, "sort": { "type": "string" }, "dir": { "type": "string" }, "q": { "type": "string" }, "visibility": { "type": "string" }, "status": { "type": "string" }, "publication_type": { "type": [ "string", "null" ] }, "publication_year": { "type": [ "integer", "null" ] }, "min_cited_by": { "type": [ "integer", "null" ] } }, "required": [ "publications", "page", "per_page", "total", "pages", "sort", "dir", "q", "visibility", "status" ]}400 - Invalid query parameters.
Section titled “400 - Invalid query parameters.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/publications/stats
Section titled “GET /api/publications/stats”Publication totals Returns aggregate publication totals used by the dashboard overview widgets. Authentication: betterAuthSession Tags: Publications
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Publication totals.
Section titled “200 - Publication totals.”Content type: application/json
{ "type": "object", "properties": { "total_publications": { "type": "integer" }, "edited_publications": { "type": "integer" }, "hidden_publications": { "type": "integer" }, "orcid_sources": { "type": "integer" } }, "required": [ "total_publications", "edited_publications", "hidden_publications", "orcid_sources" ]}GET /api/publications/stats/timeseries
Section titled “GET /api/publications/stats/timeseries”Publication timeseries Returns recent daily and monthly ingestion counts for dashboard charts. Authentication: betterAuthSession Tags: Publications
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Daily and monthly publication counts.
Section titled “200 - Daily and monthly publication counts.”Content type: application/json
{ "type": "object", "properties": { "daily_last_7_days": { "type": "array", "items": { "type": "object", "properties": { "key": { "type": "string" }, "label": { "type": "string" }, "count": { "type": "integer" } }, "required": [ "key", "label", "count" ] } }, "monthly_last_6_months": { "type": "array", "items": { "type": "object", "properties": { "key": { "type": "string" }, "label": { "type": "string" }, "count": { "type": "integer" } }, "required": [ "key", "label", "count" ] } } }, "required": [ "daily_last_7_days", "monthly_last_6_months" ]}GET /api/publications/{id}
Section titled “GET /api/publications/{id}”Get publication
Returns the original, edited, and merged publication payload along with linked people and promoted metadata. Dashboard-only custom fields are returned on detail responses as nullable top-level values and merged into the merged object: q is any integer, q_percentage is an integer from 0 to 100, if_value and if5a_value are decimal numbers, and funding is true, false, or null. These fields are not exposed by the public or integrations APIs.
Authentication: betterAuthSession
Tags: Publications
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Normalized or raw DOI identifier. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Publication detail payload.
Section titled “200 - Publication detail payload.”Content type: application/json
{ "type": "object", "properties": { "id": { "type": "string" }, "author_match_state": { "type": "string", "enum": [ "needs_attention", "complete" ] }, "person_orcids": { "type": "array", "items": { "type": "string" } }, "person": { "type": "array", "items": { "type": "object", "additionalProperties": true } }, "visible": { "type": "boolean" }, "original": { "type": "object", "additionalProperties": true }, "edited": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] }, "merged": { "type": "object", "additionalProperties": true }, "apa_citation": { "type": [ "string", "null" ] }, "doi_url": { "type": "string" }, "publication_date": { "type": [ "string", "null" ] }, "publication_year": { "type": [ "integer", "null" ] }, "publication_type": { "type": [ "string", "null" ] }, "cited_by_count": { "type": [ "integer", "null" ] }, "is_retracted": { "type": "boolean" }, "open_access_status": { "type": [ "string", "null" ] }, "enriched_metadata": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] }, "q": { "type": [ "integer", "null" ], "description": "Dashboard-only quartile value. Any integer is accepted." }, "q_percentage": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 100, "description": "Dashboard-only percentile value." }, "if_value": { "type": [ "number", "null" ], "description": "Dashboard-only impact factor value. Comma decimal input is normalized before persistence." }, "if5a_value": { "type": [ "number", "null" ], "description": "Dashboard-only five-year impact factor value. Comma decimal input is normalized before persistence." }, "funding": { "type": [ "boolean", "null" ], "description": "Dashboard-only nullable funding flag." }, "edit_patch": { "type": [ "string", "null" ] }, "last_fetched_at": { "type": "integer" }, "last_edited_at": { "type": [ "integer", "null" ] }, "edit_count": { "type": "integer" }, "provider_sources": { "type": "object", "additionalProperties": true } }, "required": [ "id", "author_match_state", "person_orcids", "person", "visible", "original", "edited", "merged", "doi_url", "is_retracted", "q", "q_percentage", "if_value", "if5a_value", "funding", "last_fetched_at", "last_edited_at", "edit_count", "provider_sources" ], "additionalProperties": true}400 - Missing identifier.
Section titled “400 - Missing identifier.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Publication not found.
Section titled “404 - Publication not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}PATCH /api/publications/{id}
Section titled “PATCH /api/publications/{id}”Patch publication
Applies a JSON Patch document to publication metadata or toggles visibility. Dashboard-only custom field paths are accepted for /q, /q_percentage, /if_value, /if5a_value, and /funding; comma decimal input for IF fields is normalized before persistence.
Authentication: betterAuthSession
Tags: Publications
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Normalized or raw DOI identifier. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "array", "items": { "type": "object", "properties": { "op": { "type": "string" }, "path": { "type": "string" }, "value": {} }, "required": [ "op", "path" ], "additionalProperties": true }}Responses
Section titled “Responses”200 - Patch result.
Section titled “200 - Patch result.”Content type: application/json
{ "type": "object", "properties": { "ok": { "type": "boolean" }, "visible": { "type": "boolean" }, "edit_count": { "type": "integer" } }, "required": [ "ok", "visible" ], "additionalProperties": true}400 - Invalid patch payload.
Section titled “400 - Invalid patch payload.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Publication not found.
Section titled “404 - Publication not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}DELETE /api/publications/{id}
Section titled “DELETE /api/publications/{id}”Soft-delete publication Marks a publication as deleted without removing the row. Authentication: betterAuthSession Tags: Publications
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Normalized or raw DOI identifier. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - Deletion acknowledged.
Section titled “200 - Deletion acknowledged.”Content type: application/json
{ "type": "object", "properties": { "success": { "type": "boolean" } }, "required": [ "success" ]}400 - Missing identifier.
Section titled “400 - Missing identifier.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/publications/bulk
Section titled “POST /api/publications/bulk”Bulk publication action Soft-deletes or restores a list of publications. Authentication: betterAuthSession Tags: Publications
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "ids": { "type": "array", "items": { "type": "string" } }, "action": { "type": "string", "enum": [ "delete", "restore" ] } }, "required": [ "ids", "action" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Bulk action acknowledged.
Section titled “200 - Bulk action acknowledged.”Content type: application/json
{ "type": "object", "properties": { "success": { "type": "boolean" } }, "required": [ "success" ]}400 - Invalid bulk request body.
Section titled “400 - Invalid bulk request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}Site Tools
Section titled “Site Tools”| Method | Path | Summary |
|---|---|---|
| POST | /api/site-tools/reset-data | Reset dashboard data |
| POST | /api/site-tools/clear-stuck-jobs | Clear stuck jobs |
| POST | /api/site-tools/repair-author-links | Repair author links for a DOI |
| POST | /api/site-tools/repair-author-links/bulk | Bulk repair author links |
| GET | /api/site-tools/api-keys | List API keys |
| POST | /api/site-tools/api-keys | Create API key |
| PATCH | /api/site-tools/api-keys/{keyHash} | Update API key |
| DELETE | /api/site-tools/api-keys/{keyHash} | Revoke API key |
| POST | /api/site-tools/api-keys/{keyHash}/rotate | Rotate API key |
POST /api/site-tools/reset-data
Section titled “POST /api/site-tools/reset-data”Reset dashboard data Administrative endpoint to delete dashboard data across shards. Protected by additional email checks. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "confirmation": { "type": "string" }, "dryRun": { "type": "boolean" }, "clearIngestionJobs": { "type": "boolean" } }, "required": [ "confirmation" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Reset result.
Section titled “200 - Reset result.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request or confirmation mismatch.
Section titled “400 - Invalid request or confirmation mismatch.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}500 - One or more shards failed.
Section titled “500 - One or more shards failed.”Content type: application/json
{ "type": "object", "additionalProperties": true}POST /api/site-tools/clear-stuck-jobs
Section titled “POST /api/site-tools/clear-stuck-jobs”Clear stuck jobs Administrative endpoint to force-fail stale queued or processing ingestion jobs across shards. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "thresholdSeconds": { "type": "integer", "minimum": 60, "maximum": 86400 }, "dryRun": { "type": "boolean" }, "reason": { "type": "string", "minLength": 1, "maxLength": 200 } }, "additionalProperties": false}Responses
Section titled “Responses”200 - Clear stuck jobs result.
Section titled “200 - Clear stuck jobs result.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}500 - One or more shards failed.
Section titled “500 - One or more shards failed.”Content type: application/json
{ "type": "object", "additionalProperties": true}POST /api/site-tools/repair-author-links
Section titled “POST /api/site-tools/repair-author-links”Repair author links for a DOI Administrative endpoint to reconcile author matches for a single publication DOI. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "doi": { "type": "string", "minLength": 1 }, "dryRun": { "type": "boolean" }, "cesamOnly": { "type": "boolean" } }, "required": [ "doi" ], "additionalProperties": false}Responses
Section titled “Responses”200 - Repair result for one publication.
Section titled “200 - Repair result for one publication.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request body or DOI.
Section titled “400 - Invalid request body or DOI.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - Publication not found.
Section titled “404 - Publication not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/site-tools/repair-author-links/bulk
Section titled “POST /api/site-tools/repair-author-links/bulk”Bulk repair author links Administrative endpoint to scan multiple publications per shard and reconcile author links in bulk. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "dryRun": { "type": "boolean" }, "cesamOnly": { "type": "boolean" }, "limitPerShard": { "type": "integer", "minimum": 1, "maximum": 1000 } }, "additionalProperties": false}Responses
Section titled “Responses”200 - Bulk repair result.
Section titled “200 - Bulk repair result.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}GET /api/site-tools/api-keys
Section titled “GET /api/site-tools/api-keys”List API keys Administrative endpoint to list API keys used for partner machine-to-machine access. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - API key list.
Section titled “200 - API key list.”Content type: application/json
{ "type": "object", "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/site-tools/api-keys
Section titled “POST /api/site-tools/api-keys”Create API key Administrative endpoint to create a scoped API key for partner integrations. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”No parameters.
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "client_name": { "type": "string", "minLength": 1, "maxLength": 120 }, "scopes": { "minItems": 1, "type": "array", "items": { "type": "string", "enum": [ "ingest", "membership.update" ] } }, "rate_limit_per_minute": { "default": 60, "type": "integer", "minimum": 1, "maximum": 10000 } }, "required": [ "client_name", "scopes", "rate_limit_per_minute" ], "additionalProperties": false}Responses
Section titled “Responses”201 - API key created. Plaintext key is returned once.
Section titled “201 - API key created. Plaintext key is returned once.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid request body.
Section titled “400 - Invalid request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}PATCH /api/site-tools/api-keys/{keyHash}
Section titled “PATCH /api/site-tools/api-keys/{keyHash}”Update API key Administrative endpoint to update active state, scopes, or rate limit of an API key. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| keyHash | path | string | yes | SHA-256 hash of the API key. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "is_active": { "type": "boolean" }, "scopes": { "minItems": 1, "type": "array", "items": { "type": "string", "enum": [ "ingest", "membership.update" ] } }, "rate_limit_per_minute": { "type": "integer", "minimum": 1, "maximum": 10000 } }, "additionalProperties": false}Responses
Section titled “Responses”200 - API key updated.
Section titled “200 - API key updated.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid hash or request body.
Section titled “400 - Invalid hash or request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - API key not found.
Section titled “404 - API key not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}DELETE /api/site-tools/api-keys/{keyHash}
Section titled “DELETE /api/site-tools/api-keys/{keyHash}”Revoke API key Administrative endpoint to deactivate an API key. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| keyHash | path | string | yes | SHA-256 hash of the API key. |
Request Body
Section titled “Request Body”No request body.
Responses
Section titled “Responses”200 - API key revoked.
Section titled “200 - API key revoked.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid hash.
Section titled “400 - Invalid hash.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - API key not found.
Section titled “404 - API key not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}POST /api/site-tools/api-keys/{keyHash}/rotate
Section titled “POST /api/site-tools/api-keys/{keyHash}/rotate”Rotate API key Administrative endpoint to issue a new key for an existing client and optionally revoke the old one. Internal: yes Authentication: betterAuthSession Tags: Site Tools
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
| keyHash | path | string | yes | SHA-256 hash of the API key being rotated. |
Request Body
Section titled “Request Body”Content type: application/json
{ "type": "object", "properties": { "revoke_old": { "default": true, "type": "boolean" } }, "required": [ "revoke_old" ], "additionalProperties": false}Responses
Section titled “Responses”200 - API key rotated. Plaintext new key is returned once.
Section titled “200 - API key rotated. Plaintext new key is returned once.”Content type: application/json
{ "type": "object", "additionalProperties": true}400 - Invalid hash or request body.
Section titled “400 - Invalid hash or request body.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}403 - Forbidden for non-admin identities.
Section titled “403 - Forbidden for non-admin identities.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}404 - API key not found.
Section titled “404 - API key not found.”Content type: application/json
{ "type": "object", "properties": { "error": { "type": "string" }, "details": { "oneOf": [ { "type": "object", "additionalProperties": true }, { "type": "null" } ] } }, "required": [ "error" ], "additionalProperties": true}