Active operational procedures for partner API keys and person affiliation updates.
This runbook documents active operations for API keys, partner writes, and person affiliation management.
Partner API Key Operations
Section titled “Partner API Key Operations”Partner write access for https://api.legaciti.org is controlled by scoped API keys in api_keys.
Supported scopes:
ingestforPOST /api/ingestmembership.updateforPOST /api/partners/people/:orcid/membership
Dashboard API Admin Endpoints
Section titled “Dashboard API Admin Endpoints”All endpoints require a valid Better Auth session and superadmin privileges.
GET /api/site-tools/api-keysPOST /api/site-tools/api-keysPATCH /api/site-tools/api-keys/:keyHashDELETE /api/site-tools/api-keys/:keyHashPOST /api/site-tools/api-keys/:keyHash/rotate
Create a key:
curl -X POST https://my.legaciti.org/api/site-tools/api-keys \ -H "Cookie: better-auth.session_token=$SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "client_name": "partner membership bot", "scopes": ["membership.update"], "rate_limit_per_minute": 60 }'Rotate and revoke old key:
curl -X POST https://my.legaciti.org/api/site-tools/api-keys/<key_hash>/rotate \ -H "Cookie: better-auth.session_token=$SESSION_TOKEN" \ -H "Content-Type: application/json" \ -d '{"revoke_old": true}'Partner Membership Update API
Section titled “Partner Membership Update API”Partners call the public API with X-API-Key.
curl -X POST "https://api.legaciti.org/api/partners/people/<orcid>/membership" \ -H "X-API-Key: $PARTNER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "entity_id": "cesam", "status": "cesam" }'Request body:
entity_idmust becesamstatusiscesamorexternalstarts_atis optional and uses unix milliseconds
Expected responses:
200affiliation update stored successfully400invalid request body401missing, invalid, or inactive key403valid key without required scope404person or entity not found
Dashboard Person Affiliation Operations
Section titled “Dashboard Person Affiliation Operations”Internal affiliation routes:
GET /api/peopleGET /api/people/:orcidPATCH /api/people/:orcid/affiliation
Affiliation update payload:
{ "entity_id": "cesam", "status": "cesam", "starts_at": 1711270800000}Behavior:
- prior active period is closed by setting
ends_at - new active period is inserted with
ends_at = null
People Query Filters
Section titled “People Query Filters”GET /api/people supports:
qpageperPagesortasnameorlast_fetched_atdirasascordescentity_idaffiliation_statusascesamorexternalactive_onlyastrueorfalse(defaultfalse)category_id
Activity Operations
Section titled “Activity Operations”Dashboard activity endpoints:
GET /api/activityPOST /api/activity/cleanup
Access model:
- Read access: any workspace member role
- Cleanup access: only
workspace_adminandsuperadmin
Default timeline behavior:
- If
fromandtoare omitted,/api/activityreturns the last 30 days.
Activity Event Taxonomy (Summary)
Section titled “Activity Event Taxonomy (Summary)”Key event families:
- Publication edits and lifecycle:
publication.edited,publication.deleted,publication.restored,publication.visibility_changed - Workspace membership changes:
workspace.user.invited,workspace.user.invite_cancelled,workspace.user.removed,workspace.user.role_updated - Ingestion requests and processing:
ingestion.requested,ingestion.job.started,ingestion.job.completed,ingestion.job.partial_failure,ingestion.work.failed,ingestion.job.failed - Activity cleanup action:
workspace.settings.activity_cleared
Severity and status conventions:
severity:info,warn,errorstatus:success,partial,failure
Activity Troubleshooting Queries (D1)
Section titled “Activity Troubleshooting Queries (D1)”Most recent 200 failures in a workspace:
SELECT id, occurred_at, event_type, actor_id, message, metadata_jsonFROM activity_eventsWHERE entity_id = ? AND status = 'failure'ORDER BY occurred_at DESCLIMIT 200;Partial ingestion failures in last 24 hours:
SELECT occurred_at, resource_id AS job_id, message, metadata_jsonFROM activity_eventsWHERE entity_id = ? AND event_type = 'ingestion.job.partial_failure' AND occurred_at >= ?ORDER BY occurred_at DESC;Cleanup actions audit:
SELECT occurred_at, actor_id, message, metadata_jsonFROM activity_eventsWHERE entity_id = ? AND event_type = 'workspace.settings.activity_cleared'ORDER BY occurred_at DESCLIMIT 100;Activity Cleanup Governance
Section titled “Activity Cleanup Governance”- Require an explicit maintenance ticket before running cleanup.
- Prefer
mode=before_dateovermode=allto preserve recent diagnostics. - Capture reason and approver in the maintenance ticket before execution.
- Validate cleanup impact by comparing counts before/after and recording
deleted_count.
Production Verification Checklist (Worker Activity)
Section titled “Production Verification Checklist (Worker Activity)”After deploying the dashboard (my.legaciti.org), worker-ingestion-orchestrator, and worker-ingestion-process:
- Trigger ingestion via
POST /api/ingestand verifyingestion.requestedappears in/api/activity. - Verify worker-generated events (
ingestion.job.startedplus completion or failure) are present for the same workspace. - Confirm workspace scoping by checking events are not visible from another workspace context.
- Verify
workspace_membercannot callPOST /api/activity/cleanup(expect403). - Verify
workspace_admincan call cleanup and thatworkspace.settings.activity_clearedis logged. - Confirm Activity page renders newly generated events and filters correctly in production UI.
Troubleshooting
Section titled “Troubleshooting”Person not visible in default filtered views:
- Verify active affiliation for the current workspace entity.
- Patch affiliation to
status: "cesam"when needed.
Partner gets 403 on membership updates:
- Verify the key includes
membership.update. - Verify the key is still active.
Last updated: March 28, 2026