Lookups
A lookup retrieves one booking. It starts with a request, runs in the background, and ends with the reservation or the reason it couldn't get one.
Start a lookup
POST/v1/lookups
| Field | Required | What it is |
|---|---|---|
airline | Yes | The airline's two-letter IATA code, like LH |
pnr | Yes | The booking reference, as the passenger has it |
surname | Yes | The passenger's surname, as it is on the booking |
passenger | For some airlines | first_name, email or ticket_number, when the airline asks for more. Airlines says which |
consent | Yes | passenger_authorised: your confirmation that the passenger asked you to retrieve this booking |
fresh | No | true reads the booking again even if we read it in the last ten minutes. See Repeats |
Send an Idempotency-Key header unique to the request, such as your order ID. If the request is retried with the same
key within 24 hours, you get the same lookup back rather than a second one.
Statuses
| Status | What it means | What you do |
|---|---|---|
queued | Accepted, waiting its turn | Nothing |
processing | The booking is being read | Nothing |
action_required | The airline asked for something only the passenger has | Ask them, then send it back |
manual_review | A person is finishing it, if you turned that on | Nothing: a webhook follows |
completed | Retrieved and confirmed against the published schedule | Read reservation |
failed | Couldn't be retrieved, and error says why | See Failures |
expired | The passenger's answer didn't arrive in time | Start a new lookup |
Every change of status sends a lookup.status_changed webhook. Only completed counts toward your
plan.
When the airline asks the passenger
Some airlines send the passenger a code, or ask for a detail the booking reference doesn't cover. The lookup moves to
action_required and says what's needed:
{
"id": "lkp_01JC9V2M8R",
"status": "action_required",
"required_input": {
"type": "one_time_code",
"prompt": "Enter the code the airline sent to the passenger's email",
"expires_at": "2026-12-04T09:24:05Z"
}
}Show the prompt to your user, then send their answer:
POST/v1/lookups/{id}/input
curl https://api.pnrstack.com/v1/lookups/lkp_01JC9V2M8R/input \
-H "Authorization: Bearer pk_live_…" \
-H "Content-Type: application/json" \
-d '{ "value": "482913" }'The lookup carries on from where it stopped. type is one of one_time_code, email, first_name, ticket_number
or date_of_birth. A code has to arrive within 10 minutes and anything else within an hour, because the airline's
session is held open until then. After expires_at the lookup is expired, and isn't charged.
Failures
A failed lookup has an error with a code, a message for your logs, and retryable:
| Code | What happened | Retry |
|---|---|---|
invalid_credentials | The airline didn't accept that booking reference and surname together | No |
booking_not_found | The airline has no booking with that reference | No |
unsupported_airline | We don't retrieve from that airline. Airlines lists the ones we do | No |
airline_unavailable | The airline's site was down or not answering | Yes, after a few minutes |
extraction_unverified | We read the booking but couldn't confirm its details | Yes |
rate_limited | Too many lookups on your account at once | Yes, after Retry-After |
A failed lookup is never charged.
Repeats
A lookup for the same airline, booking reference and surname within ten minutes of a completed one returns that result
at once, and isn't charged. Send "fresh": true when you need the booking read again; that one counts as a new lookup.