Agent-facing instructions for operating RefHub through its public API (v2). Covers the full API surface across both auth modes — API key for data routes, session JWT for management routes. No local state, no Supabase direct access, no invented behavior.
Apply whenever the user asks you to:
- read, search, or export vault contents
- add, update, delete, or import references
- create or configure vaults (name, visibility, collaborators)
- manage tags or relations on vault items
- sync changes incrementally
- enrich incomplete publication metadata from Semantic Scholar
- upload a PDF and store it in the user's linked Google Drive
- manage API keys or Google Drive settings
Do not apply for features with no API route: vault archiving, item revision history, item move/copy between vaults, webhooks.
If the refhub CLI is available (which refhub succeeds), use it instead of making HTTP calls directly.
export REFHUB_API_KEY=rhk_<publicId>_<secret> # for data routes
refhub --help # discover commands
refhub vaults --help # group-level helpExit codes: 0 success · 1 API error · 2 bad arguments · 3 auth error.
The CLI covers data routes, API-key Semantic Scholar discovery/enrichment, and API-key item-scoped PDF upload. For account setup routes (Google Drive link management, key management) fall back to direct HTTP (base URL: https://refhub-api.netlify.app/api/v1).
Two modes — never mix them.
API key (all data routes):
Authorization: Bearer rhk_<publicId>_<secret>
Session JWT (management routes — key lifecycle, Google Drive, Semantic Scholar enrichment, PDF upload):
Authorization: Bearer <supabase-session-jwt>
Sending an API key to a management route returns 401 refhub_api_key_not_supported. Switch to JWT for that call — do not retry with the API key.
Scope requirements:
| Scope | Grants |
|---|---|
vaults:read |
list/read vaults, search, stats, changes, audit |
vaults:write |
add/update/delete items, tags, relations, import |
vaults:export |
export vault as JSON or BibTeX |
vaults:admin |
create/update/delete vaults, visibility, shares |
If a credential is missing or has insufficient scope: stop, report clearly, ask the user to provide a valid key. Do not attempt workarounds.
- Never infer
vault_idfrom a vault name. Always callGET /vaultsand resolve it from the response. - Use the right auth for the right route. Management routes (key management, Google Drive link management, global audit) require a session JWT and reject API keys with
401 refhub_api_key_not_supported. Data routes require an API key. - Never assume a tag exists. List tags before using
tag_idsin any write. - Never create tags implicitly during item writes. Tag creation is a separate step.
- Never retry a bulk write after ambiguous failure unless you have an
idempotency_key. - Never assume frontend capability equals API support. UI features backed by direct Supabase may not have a public API route.
- Never proceed with vault or item deletion without explicit user confirmation. Both are hard deletes with no undo.
- Never send
visibilityorpublic_sluginPATCH /vaults/:vaultId. Use the dedicated visibility endpoint. tag_idson item update is a full replacement, not an append. Make this explicit to the user before patching.
GET /vaults # list (vaults:read)
GET /vaults/:vaultId # read with items/tags/relations (vaults:read)
POST /vaults # create (vaults:admin)
PATCH /vaults/:vaultId # update metadata (vaults:admin + owner)
DELETE /vaults/:vaultId # hard delete — confirm first (vaults:admin + owner)
PATCH /vaults/:vaultId/visibility # set visibility/slug (vaults:admin + owner)
GET /vaults/:vaultId/shares # list collaborators (vaults:read)
POST /vaults/:vaultId/shares # add collaborator — email required, role: viewer|editor (vaults:admin)
PATCH /vaults/:vaultId/shares/:shareId # update role (vaults:admin)
DELETE /vaults/:vaultId/shares/:shareId # remove collaborator (vaults:admin)
Notes:
visibilityvalues:private|protected|publicpublicrequires a uniquepublic_slug(lowercase alphanumeric + hyphens)- Adding a share to a
privatevault auto-upgrades it toprotected - Vault delete cascades: all items, tags, shares, and key restrictions are removed
POST /vaults/:vaultId/items # add one or more items (vaults:write)
PATCH /vaults/:vaultId/items/:itemId # update item (vaults:write)
DELETE /vaults/:vaultId/items/:itemId # hard delete — confirm first (vaults:write)
POST /vaults/:vaultId/items/upsert # bulk upsert by DOI / bibtex_key (vaults:write)
POST /vaults/:vaultId/items/import-preview # dry-run upsert — writes nothing (vaults:read)
Notes:
- Each item must include
title authorsis a string array e.g.["Smith J", "Doe A"], not a plain stringtag_idsmust reference existing vault tagstag_idson update replaces the full tag set — not additive- Bulk upsert matches on DOI first, then
bibtex_key; items with neither are always created - Pass
idempotency_keyon bulk upsert for safe retries (TTL: 5 minutes) - Item delete removes the
vault_publicationsrow; the underlyingpublicationsrow is preserved
POST /vaults/:vaultId/import/doi # from DOI (vaults:write)
POST /vaults/:vaultId/import/bibtex # from BibTeX string (vaults:write)
POST /vaults/:vaultId/import/url # from URL via Open Graph (vaults:write)
Notes:
- DOI import calls Semantic Scholar server-side; returns
409if DOI already exists in vault - BibTeX import skips entries where
bibtex_keyalready exists; returns{ created, skipped } - URL import is best-effort: creates item with whatever metadata is available
GET /vaults/:vaultId/tags # list (vaults:read)
POST /vaults/:vaultId/tags # create { name, color?, parent_id? } (vaults:write)
PATCH /vaults/:vaultId/tags/:tagId # update { name?, color?, parent_id? } (vaults:write)
DELETE /vaults/:vaultId/tags/:tagId # delete; child tags bubble up to parent (vaults:write)
POST /vaults/:vaultId/tags/attach # attach { item_id, tag_ids } — idempotent (vaults:write)
POST /vaults/:vaultId/tags/detach # detach { item_id, tag_ids } — ignores unattached (vaults:write)
GET /vaults/:vaultId/relations?type= # list; only type filter supported (vaults:read)
POST /vaults/:vaultId/relations # create — not idempotent, check before creating (vaults:write)
PATCH /vaults/:vaultId/relations/:id # update relation_type only (vaults:write)
DELETE /vaults/:vaultId/relations/:id # delete (vaults:write)
Notes:
publication_idandrelated_publication_idare the item'sidfield (thevault_publicationsrow id), notoriginal_publication_id- Supported types:
citesextendsbuilds_oncontradictsreviewsrelated - Duplicate pair returns an error — list relations before creating
GET /vaults/:vaultId/search?q=&author=&year=&doi=&tag=&type=&page=&per_page= # (vaults:read)
GET /vaults/:vaultId/stats # item/tag/relation counts + last_updated (vaults:read)
GET /vaults/:vaultId/changes?since= # items updated after ISO timestamp (vaults:read)
per_pagedefault 25, max 100
These routes are API-key-compatible under /semantic-scholar/* and require vaults:read. Legacy root routes remain JWT/browser frontend routes.
POST /semantic-scholar/doi-metadata # { doi } → full metadata; null if not found
POST /semantic-scholar/lookup # { doi } or { title } → Semantic Scholar paper_id
POST /semantic-scholar/search # { query, limit? } → topic/keyword search
POST /semantic-scholar/recommendations # { paper_id, limit? } → recommended papers
POST /semantic-scholar/related # alias for recommendations
POST /semantic-scholar/references # { paper_id, limit? } → papers this paper cites
POST /semantic-scholar/citations # { paper_id, limit? } → papers citing this paper
POST /semantic-scholar/cited-by # alias for citations
CLI: refhub discover ... for lookup/search/graph traversal, and refhub enrich --vault <id> [--item <id>] [--dry-run] for DOI metadata enrichment.
Requires vaults:write and Google Drive already linked to the account through the web UI.
POST /vaults/:vaultId/items/:itemId/pdf # raw application/pdf bytes → stored in Drive
publicationIdisoriginal_publication_idfrom the vault item, not the item'sid- Max 26 MB
- CLI:
refhub pdf upload --vault <vaultId> --item <itemId> --file <path.pdf> - Errors:
404 publication_not_found·503 drive_not_linked·502 drive_upload_failed
GET /vaults/:vaultId/export?format=json|bibtex # (vaults:export)
GET /vaults/:vaultId/audit?since=&until=&per_page=&page= # vault-scoped (any API key)
GET /audit?since=&until=&per_page=&page= # global (JWT only)
- Audit
per_pagedefault 25, max 100
| Status | Action |
|---|---|
401 missing_api_key / invalid_api_key_format / expired / revoked |
Stop. Report clearly. Ask user for a valid key. Do not retry with same key. |
401 refhub_api_key_not_supported |
API key sent to a JWT-only route. Switch auth mode or stop. |
403 missing_scope |
Report which scope is needed. Do not attempt a workaround. |
403 vault_access_denied / vault_not_found |
Report and stop. |
404 |
Resource doesn't exist. Verify the id. Do not create a replacement silently. |
409 |
Already exists (DOI import, duplicate relation). Surface the existing resource id. |
413 request_too_large |
Split into smaller batches. |
429 rate_limit_exceeded |
Back off using retry_after_seconds. |
500 bulk_insert_partial_failure |
Partial write may have occurred. Do not retry without idempotency_key. Alert user for manual review. |
5xx |
Retry once with backoff. If it persists, report and stop. |
Every error response includes error.code, error.message, and meta.request_id. Always surface request_id when reporting failures.
Do not attempt these — the current public API does not support them:
- Vault archiving, soft-delete, or restore
- Item revision history or restore
- Item move or copy between vaults
- Webhooks or event subscriptions
- Direct Supabase access as a fallback
State this clearly if the user requests one; do not improvise an alternative.
Normal agent runtime is API-key-only:
- Semantic Scholar:
POST /api/v1/semantic-scholar/lookup,/doi-metadata,/search,/recommendations,/related,/references,/citations,/cited-by; all requirevaults:read. CLI:refhub discover ...andrefhub enrich --vault <id> [--item <id>] [--dry-run]. - Item PDF upload:
POST /api/v1/vaults/:vaultId/items/:itemId/pdfwith rawapplication/pdfbytes; requiresvaults:writeand a Google Drive account already linked in the RefHub web UI. CLI:refhub pdf upload --vault <vaultId> --item <itemId> --file <path.pdf>. - Google Drive connect/disconnect, API-key lifecycle, legacy
/publications/:publicationId/pdf, and global audit remain session-JWT/browser account-management flows. - Search/list accepts canonical
per_pageandtag; backend also accepts compatibility aliaseslimitandtag_id. DOI filtering is supported.