Documentation
¶
Overview ¶
Package openalex is the client for the OpenAlex API — the source of works, metadata, resolved reference lists and open-access locations.
No API key is required. The cost model was measured, not taken from the documentation (D11, spike 3), and it is what shapes this package:
- A single work by ID is free. A list request costs 1 credit whatever the page size, and a title search costs 10. The daily allowance is 1,000.
- At most 100 IDs may be piped into a filter; 101 is a hard 400. So batches are cut at MaxBatch, never "as many as fit".
- The request rate runs out long before the credits do: the first 429 appeared around 11 req/s, so HTTPOptions sets the bucket at 5.
Batch by default, for round trips rather than credits. Send mailto because it is asked for; it bought no measurable throughput (D11).
referenced_works arrives already resolved, inside the work object, complete. Do not write a reference parser (hard rule 2).
This package returns model types and never imports store. Deciding what to write, and in which transaction, is internal/graph's job.
Index ¶
- Constants
- func HTTPOptions(contact string, cache httpx.Cache) httpx.Options
- type Batch
- type Candidate
- type Client
- func (c *Client) GetLocations(ctx context.Context, ids []string) (Located, error)
- func (c *Client) GetWorksBatch(ctx context.Context, ids []string) (Batch, error)
- func (c *Client) Resolve(ctx context.Context, id identity.ID) (model.HydratedWork, error)
- func (c *Client) SearchByTitle(ctx context.Context, title string, limit int) ([]Candidate, error)
- type Located
Constants ¶
const ( // BaseURL is the OpenAlex API. BaseURL = "https://api.openalex.org" // MaxBatch is the most IDs one filter may carry. Measured exactly: 100 // returns all of them, 101 is a hard 400 (spike 1). A hard chunk size, not // a hopeful one. MaxBatch = 100 // RatePerSecond is the token bucket for OpenAlex: half the ~11 req/s at // which the first 429 appeared (spike 3). RatePerSecond = 5 // DefaultSearchLimit is how many title-search candidates are asked for. A // title search costs 10 credits whatever the page size, so this is about // what a person can choose between, not about cost. DefaultSearchLimit = 10 )
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Batch ¶
type Batch struct {
// Works holds every work found, in the order first requested, each once.
Works []model.HydratedWork
// Aliases maps a requested ID to the different ID OpenAlex returned for it.
// OpenAlex merges duplicate records and redirects the retired ID to the
// surviving one; the caller must fold the old node into the new, or the
// same paper sits in the graph twice (hard rule 6).
Aliases map[string]string
// Missing lists requested IDs OpenAlex has no record of, each confirmed by
// a single lookup rather than inferred from its absence in a list. The
// caller marks them unresolved and stops spending budget on them.
Missing []string
}
Batch is what GetWorksBatch found.
type Candidate ¶
type Candidate struct {
Work model.HydratedWork
// TitleMatch reports that the candidate's title is, allowing for case,
// punctuation and a typo, the one searched for. It orders and flags the
// list. It is never grounds to accept a candidate: two papers can share a
// title, and ADR-005 requires the user to choose.
TitleMatch bool
}
Candidate is one title-search result.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client reads works from OpenAlex.
func New ¶
New wraps an httpx.Client built from HTTPOptions. Build one, share it: the rate limit is only a limit if every OpenAlex request waits on the same bucket.
func (*Client) GetLocations ¶ added in v0.3.0
GetLocations reads the open-access PDF links of works by OpenAlex ID, MaxBatch to a request at one credit each — for works fetched before hydration asked for locations, and for stubs.
IDs a batch filter omits are looked up one by one, free, exactly as GetWorksBatch does (spike 1: a filter silently drops IDs that exist). If a request fails, what was found so far is returned with the error.
func (*Client) GetWorksBatch ¶
GetWorksBatch hydrates works by OpenAlex ID, MaxBatch to a request.
A batch filter silently omits any ID it cannot match, and that absence is ambiguous: the work may not exist, or it may have been merged into another record, which a filter does not follow but a single lookup does. Each omitted ID is therefore looked up on its own — free, and in practice a handful per batch — and lands in Aliases or Missing according to the answer.
If a request fails, the works already fetched are returned with the error, which wraps errs.ErrTransient when retrying later may help. The expander commits what it has and carries on (§7); the rest stay stubs on the frontier.
func (*Client) Resolve ¶
Resolve fetches the one work a deterministic identifier names.
OpenAlex IDs, DOIs and PMIDs are single-work lookups, which are free. arXiv IDs are harder than they look, because OpenAlex does not index arXiv IDs as such: arXiv mints a DataCite DOI for every paper, but OpenAlex does not always hold the work under it — 1706.03762, "Attention Is All You Need", is a 404 by its arXiv DOI (recorded 24 Sept 2026). So the DOI is tried first, free, and on a 404 the work is found by its arXiv landing page instead, for one credit.
A title is refused: it names candidates, not a work, and SearchByTitle is the call for it (ADR-005). OpenAlex having no record wraps errs.ErrUnresolved.
func (*Client) SearchByTitle ¶
SearchByTitle returns works whose titles match title, those that match closely first and otherwise in OpenAlex's relevance order. No results is an empty slice, not an error; the caller decides what that means.
A search costs 10 credits — ten times a list request — which is fine for a seed a person typed and would not be for anything in a loop.
type Located ¶ added in v0.3.0
type Located struct {
// Links maps each requested ID to its PDF links. A requested ID that
// OpenAlex merged into another record maps to the survivor's links.
Links map[string]model.PDFLinks
// Missing lists requested IDs OpenAlex has no record of, each confirmed
// by a single lookup.
Missing []string
}
Located is what GetLocations found.