Help us shape the future of Go! Take the 2026 Go Developer Survey

openalex

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

View Source
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

func HTTPOptions

func HTTPOptions(contact string, cache httpx.Cache) httpx.Options

HTTPOptions is how an httpx.Client for OpenAlex must be configured. The rate and the name of the contact parameter are facts about OpenAlex, so they live here rather than with whoever builds the client.

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

func New(h *httpx.Client) *Client

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

func (c *Client) GetLocations(ctx context.Context, ids []string) (Located, error)

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

func (c *Client) GetWorksBatch(ctx context.Context, ids []string) (Batch, error)

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

func (c *Client) Resolve(ctx context.Context, id identity.ID) (model.HydratedWork, error)

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

func (c *Client) SearchByTitle(ctx context.Context, title string, limit int) ([]Candidate, error)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL