interninbox
Find internships from your terminal.
List your target companies once, then get every matching internship from their public job boards, in one command, on your machine.
$ interninbox scan --new-only
COMPANY TITLE LOCATIONS POSTED URL
stripe Software Engineering Intern (Summer) New York, NY 2026-08-01 https://stripe.com/jobs/...
linear Product Engineering Intern Remote 2026-07-28 https://jobs.ashbyhq.com/linear/...
plaid Data Science Intern San Francisco - https://jobs.lever.co/plaid/...
3 internships across 3 companies
No accounts. No API keys. No LLMs. Nothing leaves your machine. It reads the documented public job-board APIs of Greenhouse, Lever, and Ashby (the same endpoints each company's own careers page calls) plus, optionally, the official USAJOBS API for federal Pathways internships.
Contents
- Install
- Quickstart
- Commands
- Configuration
- Finding a company's slug
- How matching works
--new-onlyand the state file- USAJOBS (optional)
- How a scan works
- Politeness, built in
- Scope, honestly
- FAQ
- Development
Install
With pipx (recommended: isolated, on your PATH):
pipx install git+https://github.com/hiratinspace/interninbox
With uv:
uv tool install git+https://github.com/hiratinspace/interninbox
Or run from a checkout:
git clone https://github.com/hiratinspace/interninbox
cd interninbox
uv sync
uv run interninbox --help
Requires Python 3.11+. PyPI publication is planned; after that,
pipx install interninbox will work directly.
Quickstart
Sixty seconds, three commands:
interninbox init # 1. writes a starter interninbox.toml here
interninbox companies # 2. prints 34 well-known companies to copy from
interninbox scan # 3. scans every configured company
Edit interninbox.toml between steps 2 and 3: add the companies you care
about, tighten the filters if you like, and re-run interninbox scan whenever
you want fresh results. Add --new-only to see only what appeared since your
last scan.
Commands
| Command | What it does |
|---|---|
interninbox init |
Write a starter interninbox.toml into the current directory (refuses to overwrite) |
interninbox scan |
Scan every configured company and print matching internships |
interninbox companies |
Print a starter list of well-known companies as ready-to-paste ats:slug entries |
interninbox --version |
Print the version |
scan flags
| Flag | Effect |
|---|---|
--config PATH |
Use a config other than ./interninbox.toml |
--json |
Emit machine-readable JSON instead of the table |
--markdown |
Emit a Markdown table (paste it anywhere) |
--new-only |
Show only listings not seen by a previous scan |
--state PATH |
Use a state file other than .interninbox-state.json next to the config |
Exit codes: 0 on success (including partial failures: a company that fails
prints a one-line warning and never aborts your scan), 1 when the config is
invalid or every company failed.
Configuration
interninbox init writes this file; every key explained:
# Target companies as "ats:slug".
companies = [
"greenhouse:stripe", # job-boards.greenhouse.io/<slug>
"lever:plaid", # jobs.lever.co/<slug>
"ashby:linear", # jobs.ashbyhq.com/<slug>
]
[filters]
# Extra title keywords that count as an internship signal, in addition to
# the built-in one (intern, internship, co-op, summer analyst, apprentice,
# student trainee, ...).
include_keywords = []
# Drop any listing whose title contains one of these (case-insensitive).
exclude_keywords = ["mechanical"]
# Keep only listings whose location contains one of these substrings
# (case-insensitive). Empty = keep every location.
locations = ["New York", "Remote"]
# When true (the default), remote listings always pass the locations filter.
# When false, remote-only listings are dropped.
remote_ok = true
# Optional: federal internships (Pathways program) via the official
# USAJOBS API; see the USAJOBS section below.
[usajobs]
enabled = true
email = "you@example.com" # the email your API key is registered under
keywords = ["software"]
api_key_env = "USAJOBS_API_KEY" # environment variable holding your key
| Key | Type | Default | Meaning |
|---|---|---|---|
companies |
list of "ats:slug" |
(required) | Boards to scan; ats is greenhouse, lever, or ashby |
filters.include_keywords |
list of strings | [] |
Extra title keywords OR-ed with the built-in internship signal |
filters.exclude_keywords |
list of strings | [] |
Title substrings that drop a listing |
filters.locations |
list of strings | [] |
Location substrings to keep; empty keeps everything |
filters.remote_ok |
bool | true |
Whether remote listings bypass the locations filter |
usajobs.enabled |
bool | false |
Turn the USAJOBS adapter on |
usajobs.email |
string | (none) | The email your USAJOBS key is registered under |
usajobs.keywords |
list of strings | [] |
Extra keyword filter passed to the USAJOBS query |
usajobs.api_key_env |
string | "USAJOBS_API_KEY" |
Name of the environment variable holding your key |
A commented copy ships as
interninbox.example.toml.
Finding a company's slug
Open the company's careers page and look at the URL of an actual job listing:
| You see | Add to your config |
|---|---|
job-boards.greenhouse.io/acme/... |
"greenhouse:acme" |
boards.greenhouse.io/acme/... |
"greenhouse:acme" |
jobs.lever.co/acme/... |
"lever:acme" |
jobs.ashbyhq.com/acme/... |
"ashby:acme" |
If a scan reports HTTP 404 from <host>: check the slug exists, the slug is wrong or the
company moved ATS providers. interninbox companies gives you 34 known-good
entries to start from.
How matching works
All matching is local, deterministic heuristics. Fast, free, and predictable:
- Internship signal: word-boundary regexes on the title:
intern,internship,co-op,summer analyst,apprentice,student trainee, and friends, OR any of yourinclude_keywords. Word boundaries matter: "International Program Manager" and "Internal Tools Engineer" do not match. - Staff-role exclusion: roles about interns rather than for them (recruiter, program manager, university relations) and unambiguous seniority markers (Senior, Staff, II/III) are dropped.
- Your filters:
exclude_keywords, thenlocations/remote_ok.
A listing with no stated location passes the locations filter (boards often omit location metadata; dropping those silently would hide real internships).
--new-only and the state file
Every scan records what it saw in a small state file
(.interninbox-state.json, next to your config; override with --state).
With --new-only, only listings absent from that file are shown, so "new"
always means "since my last scan", whether or not earlier scans used the
flag.
- First scan: everything is new.
- Missing or corrupt state file: everything counts as new: one warning, never a crash.
- The state file is per-config-location and gitignored by
init's convention; delete it any time to reset.
Run it on a schedule (cron, launchd, a shell alias you hit with your morning
coffee) and --new-only becomes a personal internship feed.
USAJOBS (optional)
Federal Pathways internships come from the official USAJOBS Search API, which requires a free key:
- Request one at https://developer.usajobs.gov/apirequest/.
- Export it:
export USAJOBS_API_KEY=...(or pointapi_key_envat your preferred variable). - Set
[usajobs] enabled = trueandemail = "...".
Per USAJOBS's documented API contract, requests to it must carry the
registered email as the User-Agent; this tool sends exactly that, for that
host only. If [usajobs] is enabled but the key variable is unset, the scan
skips it with an info line and carries on.
How a scan works
flowchart LR
A[interninbox.toml] --> B["Fetcher<br/>polite HTTP, one per scan"]
B --> C1[Greenhouse boards API]
B --> C2[Lever postings API]
B --> C3[Ashby posting API]
B --> C4["USAJOBS API<br/>(optional)"]
C1 & C2 & C3 & C4 --> D["Internship signal<br/>+ staff-role exclusion"]
D --> E["Your filters<br/>keywords, locations"]
E --> F["State diff<br/>(--new-only)"]
F --> G[Table / JSON / Markdown]
Politeness, built in
Being a good citizen is enforced in one place (src/interninbox/fetch.py)
that every adapter goes through; it is not a setting you can forget:
- Requests run sequentially, with at least 500 ms between any two requests to the same host.
- 15-second timeout, at most one retry, and only on transient failures (network errors, 5xx).
- Every request carries an honest User-Agent:
interninbox/<version> (+https://github.com/hiratinspace/interninbox). - Only documented public APIs are used: the same endpoints the companies' own careers pages call. No HTML scraping, no automation of anything behind a login.
Scope, honestly
This tool does one-shot local scans. That is its whole job, and it does it politely and fast. What it deliberately does not do:
- verify a listing is still live (boards keep stale posts around),
- deduplicate reposts across boards,
- watch continuously or alert you the moment something appears,
- apply on your behalf.
Continuous verification, curation, and instant alerts are what the hosted Interninbox product (coming soon) does. This CLI is the honest local version: you run it, you own your data, nothing phones home.
FAQ
Why only Greenhouse, Lever, and Ashby? They expose documented public board APIs designed for exactly this. Support for more sources may come; PRs welcome if the source has a documented public API.
Does it store or send my data anywhere? No. The only writes are your config and the local state file. There is no telemetry of any kind.
A company I added returns 404. The slug is wrong or the company changed ATS providers; see Finding a company's slug.
Can it email/notify me?
Not built in. Pipe --json into whatever you like, or run it on a schedule
with --new-only and a mail hook.
Development
uv sync
uv run pytest # 102 tests, all offline (MockTransport + synthetic fixtures)
uv run ruff check .
Layout: src/interninbox/ (adapters, filters, fetcher, CLI),
tests/ with authored synthetic fixtures for fictional companies; no
recorded third-party data, ever (provenance note).
Support is best-effort via GitHub issues; see CONTRIBUTING.md.
License
MIT © 2026 Interninbox contributors
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file interninbox-0.1.0.tar.gz.
File metadata
- Download URL: interninbox-0.1.0.tar.gz
- Upload date:
- Size: 30.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b1f1ee330d84436947986e56ba68ab7db2993a039c819a0215c3dee8365620aa
|
|
| MD5 |
aa1b3ee0b87fa8978d14d09e0ad706a5
|
|
| BLAKE2b-256 |
afa57447db998f86a07ea17e56c88fe375909f70bcea76b035182e59a758a690
|
Provenance
The following attestation bundles were made for interninbox-0.1.0.tar.gz:
Publisher:
release.yml on hiratinspace/interninbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
interninbox-0.1.0.tar.gz -
Subject digest:
b1f1ee330d84436947986e56ba68ab7db2993a039c819a0215c3dee8365620aa - Sigstore transparency entry: 2424671090
- Sigstore integration time:
-
Permalink:
hiratinspace/interninbox@7c00a2ce72d776b6528f960a5f7d88fe98eed37b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hiratinspace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c00a2ce72d776b6528f960a5f7d88fe98eed37b -
Trigger Event:
release
-
Statement type:
File details
Details for the file interninbox-0.1.0-py3-none-any.whl.
File metadata
- Download URL: interninbox-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af330605fa48d91e0f28505873bb8f96c08765981888c919de2699b0eee63c68
|
|
| MD5 |
c27f5a2100c2dd68c8f22e0a613c0c1d
|
|
| BLAKE2b-256 |
e6e4d37a703b3b4fa6110807ec0784142ac3a04619946662764bd6df654fc6b3
|
Provenance
The following attestation bundles were made for interninbox-0.1.0-py3-none-any.whl:
Publisher:
release.yml on hiratinspace/interninbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
interninbox-0.1.0-py3-none-any.whl -
Subject digest:
af330605fa48d91e0f28505873bb8f96c08765981888c919de2699b0eee63c68 - Sigstore transparency entry: 2424671490
- Sigstore integration time:
-
Permalink:
hiratinspace/interninbox@7c00a2ce72d776b6528f960a5f7d88fe98eed37b -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/hiratinspace
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@7c00a2ce72d776b6528f960a5f7d88fe98eed37b -
Trigger Event:
release
-
Statement type: