Blog · October 17, 2026

Build the travel history a green card or N-400 asks for: CBP records, FOIA and a trip-list script

Summarized from official sources, checked October 17, 2026. Official pages change; follow the linked source if it differs from this summary.

If you came to the U.S. on F-1, then moved to OPT, STEM OPT, H-1B and toward a green card, you'll eventually need every entry and exit date. This post is about building that list: what the forms ask for, where each record comes from, how to request older records from CBP, and a small script that turns CBP's rows into trips and flags the gaps.

Already covered elsewhere on EADly, so not repeated here:

Not legal advice. This summarizes official U.S. government sources. It is not legal or immigration advice. CBP says its online travel history "is not an official record for legal purposes." Give your list and evidence to your immigration attorney. EADly is not affiliated with CBP, USCIS or DHS. Last checked October 17, 2026 (PT).

Use only official sites. The I-94 site is https://i94.cbp.dhs.gov/. USCIS is uscis.gov. Start CBP FOIA requests from CBP's own FOIA page (link below).

What the forms actually ask for

Form I-485 (green card). The I-485 instructions (edition 09/18/26, page 10, Item 7, "Evidence of Continuously Maintaining a Lawful Status Since Arrival") apply to family-based and employment-based preference applicants and some others (religious workers, international broadcasters, diversity visa selectees). They say: "Include evidence for every time you entered the United States and for the time periods spent in the United States." The evidence listed includes Form I-94 "including printouts of paperless I-94 admissions," Form I-797 "for all extensions of stay and changes of nonimmigrant status," Form I-20 "including all pages containing notations by authorized school officials," and passport admission or parole stamps. There are category-specific exceptions (for example INA 245(k)), so your attorney will tell you what your category needs.

Form N-400 (citizenship), edition 01/20/25. The N-400 asks you to "List below all the trips that you have taken outside the United States during the last 5 years" (or the period that applies to your eligibility option; the form points to Part 8 of the instructions). It says: "Do not include day trips (where the entire trip was completed within 24 hours)." For any trip longer than 6 months, it points you to the "Required Evidence - Continuous Residence" section of the instructions (USCIS, N-400).

Where each record comes from

RecordWho can get itWhat to know
CBP "View Travel History" at i94.cbp.dhs.govNonimmigrants onlyArrival and departure dates and ports "for the past ten years only," including travel on an expired passport. CBP says it is "not an official record for legal purposes" (CBP I-94 FAQ)
Your most recent I-94NonimmigrantsOnly the most recent record is shown online. Lookups cover I-94s issued since 1983 for most classes (CBP I-94 FAQ). How-to: I-94 guide
I-94 at the bottom of an I-797AYou (from the approval notice)USCIS issues the I-797A "as a replacement Form I-94" (USCIS: Form I-797 types). CBP's travel history may not show USCIS changes of status or extensions (CBP I-94 FAQ)
Passport stamps, boarding passes, airline account historyYouYour own evidence, especially for gaps
CBP records by FOIAAnyone, including green-card holders and U.S. citizens"Travel Records only go back to 1982" (CBP FOIA FAQ)

Expect gaps in CBP's online history

CBP says the history "may not reflect" land departures into Mexico, air carrier reservation updates, or USCIS updates, changes of status, extensions of stay or adjustments of status. Elsewhere in the same FAQ it says certain travel may not appear, "including most land border arrivals and departures, closed loop cruise arrivals and departures" (CBP I-94 FAQ). If you've driven to Mexico or Canada or taken a cruise, plan to fill gaps from your own records.

If the online history is wrong, CBP lists two correction routes (CBP Information Center redress or DHS TRIP) and says to "select only one process when submitting your request, as submitting both may delay results" (CBP I-94 FAQ). Details: I-94 guide.

Green-card holders, citizens and older records: CBP FOIA

  • Who needs it: "United States citizens and lawful permanent residents cannot retrieve their travel history using the I-94/I-95 website." They need to "submit a request through the Freedom of Information Act (FOIA)" (CBP I-94 FAQ). It's also the route for records older than ten years, or for an official copy.
  • Online only: "Effective January 22, 2026, CBP is no longer accepting hard copy (mail/fax/email)" FOIA requests. Requests go "through the CBP SecureRelease portal or www.foia.gov" (CBP FOIA page). SecureRelease is at securerelease.us, which is not a .gov address, so open it from the link on CBP's FOIA page. (CBP's records page still shows a mailing address. The FOIA page notice says hard copy is no longer accepted.)
  • What to include for your own records: "your full name, address, date of birth, and a signed Certification of Identity form, or a perjury statement and signature or notary." If someone files for you, they need "a signed G-28 form" or "some other form of signed consent" (CBP FOIA records page).
  • Cost: "There is no fee to file a FOIA request." Requesters in the "all other requesters" category are charged for copies after 100 pages and for search time over two hours (CBP FOIA FAQ).

Step by step: build your trip list

  1. Pull CBP's "View Travel History" at i94.cbp.dhs.gov (if you're a nonimmigrant) and save the page. If trips are missing, repeat with each passport you've used in the last ten years.
  2. Copy the rows into a CSV with the header date,type,port. Use Arrival or Departure for the type and YYYY-MM-DD for dates.
  3. Run the script below. It pairs departures with arrivals and lists every row it couldn't pair.
  4. Fill the gaps from passport stamps, boarding passes, airline histories and the I-94s on your I-797A notices.
  5. Request CBP records by FOIA for anything older than ten years, anything still missing, or if you're a green-card holder.
  6. If CBP's record is wrong, file one correction request, not two (see above).
  7. Give the list and the evidence to your attorney. Report what actually happened and keep the proof. Don't change dates to match CBP.

Turn CBP's rows into trips (script)

CBP lists arrivals and departures as separate rows, while forms ask about trips. One missing row (a land trip to Mexico often has no departure row) can shift everything after it. pair_trips.py does the pairing and shows which rows need checking. It uses only Python's standard library (3.8+), runs on your computer and sends nothing anywhere.

What it does:

  • Sorts rows by date. On the same date, a departure comes before an arrival (a same-day trip).
  • Pairs each departure with the next arrival.
  • Flags arrival_without_departure and departure_without_return.
  • For each trip, prints days between (return date minus departure date) and full days outside (days between minus 1, never below 0). These are two illustrative ways to count. Neither is the official method for any form.
  • Marks trips that end more than 5 calendar months after departure, as a reminder to check with your DSO. ICE's re-entry FAQ refers to students "outside the United States for five months or fewer" (ICE, Travel), but no official page we found says how to count five months.
  • With --opt-start and --opt-end, prints how many of the days between fall inside your OPT dates. That is not your unemployment count. See our unemployment days post.

Example input (sample_history.csv, fictional):

date,type,port
2026-05-20,Departure,LAX
2025-12-18,Departure,SFO
2026-01-06,Arrival,SFO
2026-03-01,Arrival,San Ysidro
2026-06-10,Arrival,LAX

Run it:

python3 pair_trips.py sample_history.csv --opt-start 2026-06-01 --opt-end 2027-05-31

Output:

Built from CBP's travel history, which CBP says is not an official record for legal purposes. The two day counts are illustrative conventions, not the rule for any particular form. Not legal advice.

Trip 1: left 2025-12-18 SFO -> back 2026-01-06 SFO | 19 days between, 18 full days outside, 0 days in OPT dates
Trip 2: left 2026-05-20 LAX -> back 2026-06-10 LAX | 21 days between, 20 full days outside, 9 days in OPT dates

CHECK 2026-03-01 San Ysidro: arrival_without_departure
Fill gaps from passport stamps, boarding passes, I-797A notices or a CBP FOIA request.

The San Ysidro row is a land arrival from Mexico with no recorded departure. Fill that gap from your own records before you hand the list to your attorney.

Files (MIT license): pair_trips.py, test_pair_trips.py, sample_history.csv. Download: pair_trips.py · test_pair_trips.py · sample_history.csv.

#!/usr/bin/env python3
"""Pair the rows of CBP's "View Travel History" into trips.

Input: a CSV with the header  date,type,port  (ISO dates, type Arrival or
Departure), typed in from https://i94.cbp.dhs.gov/ -> "View Travel History".

    python3 pair_trips.py history.csv
    python3 pair_trips.py history.csv --opt-start 2026-06-01 --opt-end 2027-05-31

Standard library only, Python 3.8+. MIT License. Not legal advice.
"""
import argparse
import calendar
import csv
import sys
from datetime import date

BANNER = (
    "Built from CBP's travel history, which CBP says is not an official record "
    "for legal purposes. The two day counts are illustrative conventions, not the "
    "rule for any particular form. Not legal advice."
)


def add_months(d, months):
    """d plus calendar months, clamped to the month end (Aug 31 + 5 -> Jan 31)."""
    m = d.month - 1 + months
    y, m = d.year + m // 12, m % 12 + 1
    return date(y, m, min(d.day, calendar.monthrange(y, m)[1]))


def read_rows(lines):
    """Parse CSV lines (file object or list of strings) into (date, type, port) tuples."""
    reader = csv.DictReader(lines)
    reader.fieldnames = [f.strip().lower() for f in reader.fieldnames or []]
    if "date" not in reader.fieldnames or "type" not in reader.fieldnames:
        raise ValueError("first line must be the header: date,type,port")
    rows = []
    for rec in reader:
        raw_date = (rec.get("date") or "").strip()
        raw_type = (rec.get("type") or "").strip().lower()
        if not raw_date and not raw_type:
            continue  # blank row
        try:
            d = date.fromisoformat(raw_date)
        except ValueError:
            raise ValueError(f"line {reader.line_num}: bad date {raw_date!r}, use YYYY-MM-DD")
        if raw_type.startswith("d"):
            kind = "Departure"
        elif raw_type.startswith("a"):
            kind = "Arrival"
        else:
            raise ValueError(f"line {reader.line_num}: type must be Arrival or Departure")
        rows.append((d, kind, (rec.get("port") or "").strip()))
    return rows


def pair_trips(rows, opt_start=None, opt_end=None):
    """Return (trips, flags). Each Departure pairs with the next Arrival."""
    trips, flags, open_dep = [], [], None
    # Sort by date; on the same date a Departure comes before an Arrival.
    for d, kind, port in sorted(rows, key=lambda r: (r[0], r[1] != "Departure")):
        if kind == "Departure":
            if open_dep:
                flags.append({"flag": "departure_without_return", "date": open_dep[0], "port": open_dep[1]})
            open_dep = (d, port)
        elif open_dep is None:
            # CBP says land departures into Mexico are not recorded automatically.
            flags.append({"flag": "arrival_without_departure", "date": d, "port": port})
        else:
            dep, dep_port = open_dep
            open_dep = None
            days = (d - dep).days
            trip = {
                "depart": dep,
                "depart_port": dep_port,
                "return": d,
                "return_port": port,
                "days_between": days,
                "full_days_outside": max(days - 1, 0),
                # Reminder only: no official source defines how to count "5 months".
                "over_5_months": d > add_months(dep, 5),
            }
            if opt_start is not None and opt_end is not None:
                trip["days_between_in_opt"] = max(0, (min(d, opt_end) - max(dep, opt_start)).days)
            trips.append(trip)
    if open_dep:
        flags.append({"flag": "departure_without_return", "date": open_dep[0], "port": open_dep[1]})
    return trips, flags


def _at(d, port):
    return f"{d} {port}" if port else str(d)


def main(argv=None):
    p = argparse.ArgumentParser(description="Pair CBP travel-history rows into trips.")
    p.add_argument("csv", help="CSV file with header date,type,port (or - for stdin)")
    p.add_argument("--opt-start", type=date.fromisoformat, help="OPT start date, YYYY-MM-DD")
    p.add_argument("--opt-end", type=date.fromisoformat, help="OPT end date, YYYY-MM-DD")
    a = p.parse_args(argv)
    if (a.opt_start is None) != (a.opt_end is None):
        p.error("use --opt-start and --opt-end together")
    try:
        if a.csv == "-":
            rows = read_rows(sys.stdin)
        else:
            with open(a.csv, newline="", encoding="utf-8-sig") as f:
                rows = read_rows(f)
    except (OSError, ValueError) as e:
        p.error(str(e))

    trips, flags = pair_trips(rows, a.opt_start, a.opt_end)
    print(BANNER)
    print()
    if not trips:
        print("No complete trips found.")
    for i, t in enumerate(trips, 1):
        line = (
            f"Trip {i}: left {_at(t['depart'], t['depart_port'])} -> back {_at(t['return'], t['return_port'])}"
            f" | {t['days_between']} days between, {t['full_days_outside']} full days outside"
        )
        if "days_between_in_opt" in t:
            line += f", {t['days_between_in_opt']} days in OPT dates"
        if t["over_5_months"]:
            line += " | MORE THAN 5 CALENDAR MONTHS: talk to your DSO"
        print(line)
    if flags:
        print()
        for f in flags:
            print(f"CHECK {_at(f['date'], f['port'])}: {f['flag']}")
        print("Fill gaps from passport stamps, boarding passes, I-797A notices or a CBP FOIA request.")
    return 0


if __name__ == "__main__":
    sys.exit(main())

Tests: python3 -m unittest test_pair_trips.py (14 tests, including the example above, a 5-month boundary, a same-day trip, a month-end date and bad input). The trip results were also checked against an independent reference implementation on 400 random histories.

Reading the numbers:

  • Same-day trips show 0 days. The N-400 says not to list day trips completed within 24 hours. Keep them in your own records anyway.
  • "5 months" is a reminder, not a rule. The script uses calendar months (Jan 15 + 5 months = Jun 15; Aug 31 + 5 months = Jan 31).

Keep the list as you go with EADly

Rebuilding ten years of travel from CBP, old passports and airline emails is slow, and it's where gaps appear. It's easier to save each trip and each I-94 when it happens. EADly is a private, local-first app for iPhone and Android:

  • Trips are saved as boarding-pass-style cards, each with a Trip review of saved facts and a document checklist. Share to EADly brings a booking into a trip.
  • Each I-94 is its own record with its source (CBP, paper or I-797A). An older record can be marked superseded (I-94 tracker).
  • After a trip, EADly prompts you to save your new I-94.
  • Travel documents (passport, visa, I-20 signature, I-94, EAD) turn orange near expiry.
  • Quick Actions → Open CBP I-94 opens CBP's official site.
  • The free plan includes 1 trip. EADly Plus adds the immigration history summary PDF you can give your attorney.

What EADly doesn't do: it does not connect to CBP or retrieve your I-94, and it doesn't determine your status. You still get the records from the official sites. EADly keeps them organized on your device.

[SCREENSHOT NEEDED: in-app screen, My Trips list with boarding-pass-style cards, fictional sample data (SFO Dec 18–Jan 6; LAX May 20–Jun 10)]

[SCREENSHOT NEEDED: in-app screen, Trip review for one trip showing saved facts and the document checklist, fictional sample data]

[SCREENSHOT NEEDED: in-app screen, I-94 records list with source labels (CBP / paper / I-797A) and one record marked superseded, fictional sample data]

[SCREENSHOT NEEDED: in-app screen, the post-trip prompt to save the new I-94, fictional sample data]

[SCREENSHOT NEEDED: in-app screen, Quick Actions → "Open CBP I-94". Caption: "EADly opens CBP's official I-94 site. It does not connect to CBP or retrieve your I-94."]

[SCREENSHOT NEEDED: in-app screen labeled "Plus", immigration history summary PDF showing the travel and I-94 sections, fictional sample data]

Get EADly on the App Store or Google Play.

Common questions

How far back does CBP's online travel history go?

Ten years, including travel on an expired passport (CBP I-94 FAQ).

I'm a green-card holder or U.S. citizen. How do I get my travel history?

Not on the I-94 site. File a FOIA request with CBP through SecureRelease or foia.gov. CBP stopped accepting mail, fax and email requests on January 22, 2026 (CBP I-94 FAQ; CBP FOIA page).

Does a CBP FOIA request cost money?

Filing is free. "All other requesters" pay for copies after 100 pages and for search time over two hours (CBP FOIA FAQ).

How far back do CBP FOIA travel records go?

CBP says to 1982 (CBP FOIA FAQ).

Is CBP's online travel history enough for my green card application?

CBP calls it "a helpful tool" that "is not an official record for legal purposes" (CBP I-94 FAQ). The I-485 instructions ask many applicants for evidence of every entry, so add stamps, I-94s and I-797s, and ask your attorney.

Which trips go on the N-400?

All trips outside the U.S. in the last 5 years (or the period for your eligibility option), not counting day trips completed within 24 hours (N-400).

Sources

Checked October 17, 2026 (PT). Dates in brackets are the dates the pages showed.

Not legal advice. CBP says its online travel history is not an official record for legal purposes. EADly is not affiliated with CBP, USCIS or DHS.

More guides