JobsPipe
30개 이상의 구인 게시판, ATS 피드, 공공 고용 서비스에서 실시간 채용 공고를 검색하고 하나의 스키마로 통합합니다. 공고 전문을 읽고, 검색을 신호로 저장하여 새로운 매치 알림을 받으며, 회사의 기술 스택을 감지합니다. OAuth 로그인을 지원하는 원격 서버로, 무료 계정으로 시작할 수 있습니다.
문서
MCP server
Connect AI agents and MCP clients to JobsPipe — search live job postings over the Model Context Protocol, signing in with OAuth or an API key.
JobsPipe runs a Model Context Protocol server so AI agents and MCP-capable clients (Claude, ChatGPT, Cursor, and custom MCP hosts) can search live job postings directly, without you writing any HTTP code.
Add JobsPipe to your AI tool
Claude, Cursor and VS Code install in one click. The others open their setup steps below.
The server speaks MCP over Streamable HTTP at:
https://mcp.jobspipe.dev/mcp
How it works
The MCP server is a thin layer over the JobsPipe API. Every tool call that needs data is forwarded to the API under your account, so:
- Results, sources, and freshness are identical to the REST API.
- Usage counts against your plan, and your plan's quota and rate limits apply.
There are two ways to authenticate, and both land on the same account.
OAuth sign-in (default)
Clients that support MCP authorization — Claude and ChatGPT connectors, Claude Code, and other hosts that implement OAuth discovery — only need the URL. When a client connects without credentials, the server answers 401 with a WWW-Authenticate header pointing at its protected-resource metadata:
WWW-Authenticate: Bearer realm="jobspipe", resource_metadata="https://mcp.jobspipe.dev/.well-known/oauth-protected-resource"
The client follows that link to the authorization server at https://api.jobspipe.dev (metadata at /.well-known/oauth-authorization-server), registers itself, and sends you through a JobsPipe sign-in and consent screen. It uses the authorization-code flow with PKCE (S256) and refresh tokens, and then sends the access token as Authorization: Bearer <token> on every request. No key needs to be copied anywhere.
The consent screen names the app, shows the redirect URLs it registered, and lists what it is asking for. Approving lets that app search jobs and scan tech stacks as you — every call counts against your plan, the same as your own — and create or change your saved signals. It cannot see your password, change your billing, or reach anything outside your own account. Approval belongs to the account you are signed in to at that moment, and the app is only ever sent back to a redirect URL it registered.
If a token expires or is revoked, the server answers 401 with Access token is invalid or expired. Reconnect to continue. — reconnect the client to sign in again.
API key header
For clients without OAuth support, scripts, and CI, use the same API key you use for the REST API — a key that starts with jp_live_. Create or copy one from your dashboard and send it in either header:
Authorization: Bearer jp_live_xxxxxxxxxxxxxxxxxxxxxxxx
x-api-key: jp_live_xxxxxxxxxxxxxxxxxxxxxxxx
x-api-key is read only when there is no Authorization: Bearer header. A bearer value that does not start with jp_live_ is treated as an OAuth access token.
A connection with no credentials at all is rejected with 401 Unauthorized. A jp_live_ key is not validated when you connect, only when a tool reaches the API — see Limits and errors.
Install it
Pick your client. Every route lands on the same server and the same account.
Claude Code
claude mcp add --transport http jobspipe https://mcp.jobspipe.dev/mcp --scope user
Then run /mcp inside Claude Code and choose Authenticate for jobspipe — a browser opens, you sign in to JobsPipe, and the tools appear. Drop --scope user to add it to the current project only, or use --scope project to write a .mcp.json your team can commit.
To use an API key instead of signing in, pass it as a header (and skip the /mcp authenticate step):
claude mcp add --transport http jobspipe https://mcp.jobspipe.dev/mcp \
--scope user --header "Authorization: Bearer jp_live_xxxxxxxxxxxxxxxxxxxxxxxx"
A committed .mcp.json reads the key from the environment, so no secret is checked in:
{
"mcpServers": {
"jobspipe": {
"type": "http",
"url": "https://mcp.jobspipe.dev/mcp",
"headers": {
"Authorization": "Bearer ${JOBSPIPE_API_KEY}"
}
}
}
}
Claude (web, desktop and mobile)
- Open Settings → Connectors and click Add custom connector.
- Paste
https://mcp.jobspipe.dev/mcpas the remote MCP server URL. - Click Connect. Claude sends you to a JobsPipe sign-in and consent screen, and that is the whole setup — there is no key to paste and no client ID to fill in under Advanced settings.
On Team and Enterprise plans an owner adds the connector once under Organization settings → Connectors, and each member then connects their own JobsPipe account from Settings → Connectors.
ChatGPT
ChatGPT connects to JobsPipe as a plugin backed by the MCP server. It cannot send an API key, so sign-in is the only route, and it needs Developer mode once.
- Open Settings → Security and login and turn on Developer mode. It has to stay on while the plugin is installed.
- Open Settings → Plugins and click the + to create a plugin. Name it JobsPipe and enter
https://mcp.jobspipe.dev/mcpas the MCP server URL. Leave Authentication on OAuth. - Click Create, then Connect. ChatGPT registers itself, opens a JobsPipe sign-in and consent screen, and the tools become available in a chat.
Two things that look like the plugin but are not: a JobsPipe entry installed from Browse plugins that shows curl commands and asks for an API key is a skill document, not this server, and ChatGPT will answer that it has "no usable API key"; remove it and create the plugin above. And if ChatGPT never shows a sign-in, Developer mode is off.
ChatGPT cannot send an API key to a connector, so OAuth is the only route there. The server also answers the search and fetch tools that ChatGPT's deep research and company-knowledge features require, so JobsPipe can be used as a research source and every job it cites links back to the original posting.
Cursor
Or add it by hand — ~/.cursor/mcp.json for every project, .cursor/mcp.json inside one project:
{
"mcpServers": {
"jobspipe": {
"url": "https://mcp.jobspipe.dev/mcp"
}
}
}
{
"mcpServers": {
"jobspipe": {
"url": "https://mcp.jobspipe.dev/mcp",
"headers": {
"Authorization": "Bearer ${env:JOBSPIPE_API_KEY}"
}
}
}
}
Cursor has no type field for remote servers — a url is enough. Open Settings → MCP to confirm jobspipe is connected, and to sign in if you left headers out.
Codex
Add the server from the Codex CLI, then sign in:
codex mcp add jobspipe --url https://mcp.jobspipe.dev/mcp
codex mcp login jobspipe
codex mcp login opens a browser, you sign in to JobsPipe, and the tools appear in the next session.
To use an API key instead, put the server in ~/.codex/config.toml and let Codex read the key from the environment, so it is never written to the file:
[mcp_servers.jobspipe]
url = "https://mcp.jobspipe.dev/mcp"
bearer_token_env_var = "JOBSPIPE_API_KEY"
Export JOBSPIPE_API_KEY=jp_live_... in the shell that starts Codex. Codex sends it as an Authorization: Bearer header; skip codex mcp login in that case.
VS Code and GitHub Copilot
Or add it to .vscode/mcp.json — note the wrapper key is servers, not mcpServers:
{
"servers": {
"jobspipe": {
"type": "http",
"url": "https://mcp.jobspipe.dev/mcp"
}
}
}
To send an API key instead of signing in, let VS Code prompt for it once and keep it out of the file:
{
"servers": {
"jobspipe": {
"type": "http",
"url": "https://mcp.jobspipe.dev/mcp",
"headers": { "Authorization": "Bearer ${input:jobspipe-api-key}" }
}
},
"inputs": [
{
"type": "promptString",
"id": "jobspipe-api-key",
"description": "JobsPipe API key",
"password": true
}
]
}
Windsurf
In ~/.codeium/windsurf/mcp_config.json, remote servers use serverUrl:
{
"mcpServers": {
"jobspipe": {
"serverUrl": "https://mcp.jobspipe.dev/mcp",
"headers": {
"Authorization": "Bearer ${env:JOBSPIPE_API_KEY}"
}
}
}
}
Any other MCP client
Point it at https://mcp.jobspipe.dev/mcp over Streamable HTTP. Clients that implement MCP authorization need nothing else; the rest send the key as a header.
{
"mcpServers": {
"jobspipe": {
"type": "http",
"url": "https://mcp.jobspipe.dev/mcp",
"headers": {
"Authorization": "Bearer jp_live_xxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Once connected, the tools below appear automatically and the agent can call them.
Tools
| Tool | Description |
|---|---|
search | Find postings from a plain-language request, best match first, with results to cite: an id, a title and a link each. |
fetch | Read one posting in full by the id a search result carried. |
search_jobs | Search live job postings from 30+ sources, normalized into one schema, with every filter. |
create_signal | Save a search and be told when something new matches it, by email, Slack or a signed webhook. |
list_signals | List the signals on your account, with their filters, destinations and when each was last checked. |
detect_company_tech_stack | Detect the technologies a company serves on its domain, with confidence scores. |
search_companies_by_technology | Beta. Find companies whose own job postings show they use given technologies, with graded evidence per technology. |
get_company_tech_stack | Beta. Every technology one company's job postings name, graded by evidence tier, with counts and dates. |
search_documentation | Search these docs and get back the matching sections with their text and a link. |
list_pricing_plans | List JobsPipe plans with monthly price, job quota, and max results per call. |
get_account_info | Show which account the connection is signed in to, its plan, and the credits used and left this month. |
upgrade_plan | Get a Stripe checkout link for a bigger package, sized to your use unless you name one. Nothing is charged until you pay on that page. |
search, fetch, search_jobs, detect_company_tech_stack and the two beta company technology tools call the API and count against your plan. search_companies_by_technology costs 1 credit per company returned, once per calendar month; get_company_tech_stack costs 1 credit per call. A job is charged once per calendar month, so a fetch of a posting that search already returned is free. search_documentation and list_pricing_plans are answered by the MCP server itself and use no credits. get_account_info, create_signal, list_signals and upgrade_plan read or write your account and use no credits either; a signal costs no job credits to evaluate.
Every tool that uses credits also says how many are left, and warns when you are running low: see low_balance.
search and fetch
search and fetch are the pair that research and connector features look for by name — ChatGPT's deep research and company knowledge among them — so a JobsPipe search can be cited in a report like any other source. They are a simpler view of the same corpus search_jobs serves. search runs on agentic search: it plans structured searches from the request, checks hard rules on each posting and returns the postings that fit best first, up to 25. If agentic search cannot answer, search falls back to matching the request against job titles, so a call never fails for that reason.
search takes a query and, optionally, country_code (ISO 3166-1 alpha-2), city, remote, posted_within_days and limit (1 to 50, default 10; agentic search returns at most 25). It answers a results array of { id, title, url }, where url is the posting itself and is what gets cited, and a usage block saying what the call cost.
{
"results": [
{
"id": "b1f3c0d2e4a5",
"title": "Senior Data Engineer - Acme Corp (Berlin, Germany)",
"url": "https://example.com/jobs/b1f3c0d2e4a5"
}
],
"usage": { "credits_charged": 1, "jobs_already_paid": 0, "credits_remaining": 24382 }
}
usage says what that search cost: credits_charged is the credits it used, jobs_already_paid is how many of the postings were free because your account already paid for them this calendar month, and credits_remaining is what the account can still spend afterwards. It is absent on an account that is not billed per job.
fetch takes the id of a result and answers that one posting as { id, title, text, url, metadata }. text is the posting as readable prose — role, employer, location, work arrangement, employment type, seniority, salary, dates, skills and the posting body — and metadata carries the same facts as individual string fields, plus credits_charged and jobs_already_paid for what reading it cost and credits_remaining for what is left. An id that no longer resolves comes back as a tool error naming it; ids stop resolving once a posting closes.
For anything these two do not cover — salary floors, skills, sources, visa stance, language, industry or occupation codes, paging past 50 — use search_jobs below.
search_jobs
Every filter is optional and combined with AND. Array filters ending in _or match any of their values.
Text and company
| Parameter | Type | Description |
|---|---|---|
job_title_or | string[] | Match jobs whose title contains any of these phrases. |
job_title_not | string[] | Exclude jobs whose title contains any of these phrases. |
description_or | string[] | Match jobs whose description contains any of these phrases. |
company_name_or | string[] | Match jobs from any of these exact company names. |
company_name_partial_match_or | string[] | Match jobs whose company name contains any of these, e.g. ["acme"] finds "Acme Corp" and "Acme Ltd". |
employer_type_or | string[] | Keep only these kinds of employer: employer (the company itself), agency, broker. |
employer_type_not | string[] | Drop these kinds of employer. ["agency","broker"] keeps only jobs posted by the hiring company. |
min_employee_count | number | Companies with at least this many employees. Jobs whose company size is unknown are dropped unless include_unknown_size is true. |
max_employee_count | number | Companies with at most this many employees. Same rule for unknown sizes. |
include_unknown_size | boolean | Keep jobs whose company size is unknown when filtering by employee count. Most postings carry no size, so a size filter without this returns far fewer results. |
min_revenue_usd | number | Companies whose estimated annual revenue, in US dollars, is at least this amount (5000000 = $5M). |
max_revenue_usd | number | Companies whose estimated annual revenue is at most this amount. Companies with no revenue on record are dropped unless include_unknown contains "company_revenue". |
Location
| Parameter | Type | Description |
|---|---|---|
job_country_code_or | string[] | ISO country codes to include, e.g. ["US","GB"]. |
job_country_code_not | string[] | ISO country codes to exclude, e.g. ["IN"]. |
job_location_or | string[] | City or region contains any of these, e.g. ["Seattle","WA"]. Terms of three characters or fewer match whole values (WA is Washington, never Iowa). Combine with job_country_code_or to disambiguate same-named cities. |
region_or | string[] | US states and Canadian provinces as ISO 3166-2 codes, e.g. ["US-NY","CA-ON"]. More precise than job_location_or for a state or province. |
metro_code_or | string[] | US CBSA metro codes, e.g. "35620" (New York). Non-US jobs never match. |
remote | boolean | true returns remote-only, false excludes remote. |
work_arrangement_or | string[] | remote, hybrid or onsite — finer than remote, which reads false for hybrid and onsite alike. Jobs with an unknown arrangement never match. |
Source
| Parameter | Type | Description |
|---|---|---|
source_or | string[] | Match any collector source, e.g. ["linkedin","greenhouse"]. Case, spaces and punctuation are ignored; yc is an alias for ycombinator. |
source_not | string[] | Exclude sources. ["indeed","linkedin"] drops the two largest boards; pin the ATS list in source_or for ATS-only. |
Role and classification
| Parameter | Type | Description |
|---|---|---|
employment_type_or | string[] | full-time, part-time, contract, temporary, internship. |
include_unlabeled_employment_type | boolean | Also return jobs with no employment type. |
job_seniority_or | string[] | Seniority levels to include. |
include_unlabeled_seniority | boolean | Also return jobs with no seniority (most postings state none). |
skills_or | string[] | Skill slugs, e.g. ["python","kubernetes"]. |
esco_skill_id_or | string[] | ESCO skill concept IDs (exact match). |
occupation_code_or | string[] | ISCO-08 codes; 4 digits exact ("2512" Software Developers), 1-3 digits match as prefixes. |
isic_division_or | string[] | ISIC Rev.4 employer industry divisions, 2 digits ("62" Computer programming). |
Pay, perks and posting signals
| Parameter | Type | Description |
|---|---|---|
min_salary_usd | number | Posted salary (top of range, annualized USD) reaches this amount. Jobs without a posted salary never match. |
benefits_or | string[] | Benefit slugs, e.g. ["401k","health insurance"]. Structured source data only, so coverage is partial. |
visa_sponsorship_or | string[] | offers, no or citizenship_required, parsed from the posting text. Jobs that say nothing never match. |
has_recruiter_email | boolean | true for only jobs with a parsed recruiter email, false for only jobs without one. |
max_applicant_count | number | At most this many applicants. Only LinkedIn exposes counts, so jobs without one are dropped. |
max_ghost_score | number | Exclude jobs whose ghost-likelihood score (0-100) exceeds this. Unscored jobs pass. |
Dates, paging and output
| Parameter | Type | Description |
|---|---|---|
posted_at_gte | string | Only postings on or after this date (YYYY-MM-DD). |
posted_at_lte | string | Only postings on or before this date (YYYY-MM-DD). |
posted_at_max_age_days | number | Only postings newer than this many days. |
last_verified_max_age_days | number | Only postings we last confirmed live on their source within this many days. |
status | string | active (the default), closed, or any. Closed postings keep their data and are how a past hiring question is answered. |
include_unknown | string[] | Field names whose unlabelled jobs that field's filter should keep rather than drop, e.g. ["language"]. |
limit | number | Rows to return. Defaults to 25 and is clamped to your plan's max results per call (25 on Free). |
offset | number | Rows to skip, for paging. |
cursor | string | Continue an earlier search from metadata.next_cursor. |
detail | string | compact (the default) or full. The tool's own argument, not a search filter. |
include_total_results | boolean | Populate metadata.total_results (slightly slower). |
blur_company_data | boolean | Deprecated and ignored. Preview mode has been removed; every search returns the full record and is billed per job. |
About 17% of postings carry an arrangement, so work_arrangement_or returns a real but partial slice and silently drops the rest. Use remote for breadth, work_arrangement_or when hybrid and onsite must be told apart.
A typical agent call:
{
"job_title_or": ["data engineer"],
"remote": true,
"posted_at_max_age_days": 14,
"limit": 25,
"include_total_results": true
}
The response mirrors the REST API: a metadata block (with total_results when requested) and a data array of normalized postings — title, company, location, country, salary range, seniority, posting date, and the apply URL. Each posting also carries sources (every board it was seen on, not just the first), last_seen_at and verified_at (when a recheck last confirmed the posting was live, and when we last looked at all), and is_manager and job_function where they are known — is_manager is what separates a principal individual contributor from an actual director, since seniority files both under "director". See the job schema for the full shape and for how much of the corpus carries each field.
How much of each posting you get. Rows come back compact by default: the fields a list of results is read for, with the description cut to a snippet and anything empty left out. A compact row says description_truncated: true and description_chars when it cut one, so you know there is more to read. Read one posting in full with fetch, or pass detail: "full" to get every field of every row exactly as the REST API answers it.
Paging. Page with cursor: take metadata.next_cursor from a response and send it back as cursor on the next call, unchanged, with the same filters. Stop when a page comes back with no next_cursor. offset still works and is simpler for a handful of pages, but a cursor is steadier on a corpus that keeps growing and is the only way past the offset ceiling. Because limit is clamped silently to your plan's page size, a page shorter than the limit you asked for does not mean you reached the end.
What a call cost. metadata.credits_charged is what the call used and metadata.jobs_already_paid is how many rows were free because your account already paid for them this calendar month. Both are absent on an account that is not billed per job.
Technologies. Pass include_technologies: true to add each posting's graded technologies (in a compact row, e.g. "Snowflake (required)"). It is off by default and costs 1 extra credit per returned job that names at least one technology, once per job per calendar month; metadata.technologies_credits_charged reports that part of the cost. fetch takes the same optional include_technologies argument and adds a Technologies: line and a technologies metadata field to the document.
create_signal and list_signals
A signal is a saved search that tells you when something new matches it, so an agent does not have to re-run the same search on a timer. Matches are keyed on the first time a posting entered the corpus, so a repost or a backfill does not fire again, and evaluating a signal costs no job credits.
create_signal takes:
| Parameter | Type | Description |
|---|---|---|
name | string | A short label for this signal. |
filters | object | The search that defines a match, in the shape search_jobs takes, minus paging and anything that decides what counts as new — the signal keeps its own watermark. |
destinations | object[] | Where matches go: { kind: "email" | "slack" | "webhook", target, cadence: "instant" | "daily" }. Email is available on every plan. |
mode | string | jobs fires on each newly matching posting; companies (the default) fires the first time a company matches; technology_adoption (beta, offered only where it is enabled) fires the first time an established company starts hiring for a technology. |
intent | string | Free text describing what you are watching for. |
idempotency_key | string | Your own key for this signal. Send the same key on a retry and the signal already saved is replayed instead of a second one being created. |
Run the same filters through search_jobs first to see what they return before saving them.
With mode: "technology_adoption", filters takes only technology_slug_or (required, 1 to 10 slugs), company_country_code_or, tier_min (confirmed, the default, or likely) and min_prior_jobs (postings the company published before its first posting naming the technology, default 5), and no intent; job filters and unknown slugs are refused. Offered only on accounts where the beta is enabled. It fires once per company and technology. See Signals → Technology adoption for what counts as adopting.
Both tools answer the same shape — create_signal one signal, list_signals an array of signals:
{
"signal": {
"id": "9f1c2f1e-...",
"name": "Fintech hiring in Berlin",
"mode": "jobs",
"enabled": true,
"filters": { "job_title_or": ["backend engineer"], "job_country_code_or": ["DE"] },
"intent": "Berlin fintechs starting to hire backend engineers",
"grade_leads": false,
"created_at": "2026-09-22T10:00:00.000Z",
"last_evaluated_at": null,
"last_error": null,
"consecutive_failures": 0,
"destinations": [
{
"id": "7a2b3c4d-...",
"kind": "webhook",
"target": "https://example.com/hooks/jobspipe",
"cadence": "instant",
"enabled": true,
"signing_secret": "whsec_..."
}
]
}
}
A webhook destination's signing_secret is returned once, when the signal is created — store it then, because a later list_signals does not include it. See Signals for how deliveries are signed and retried.
detect_company_tech_stack
| Parameter | Type | Description |
|---|---|---|
domain | string | Required. Domain to scan, e.g. "stripe.com". URLs and www. are normalized. |
mode | string | auto (default) tries a fast HTTP fetch, then a headless render if results are thin. html or render forces one strategy. |
Returns domain, scanned_at, http_status, and a detected array — each entry with slug, name, categories, confidence, version, website, saas, and oss. Results are cached for 14 days.
This is what a company's website runs. What it hires for, from its own job postings, is get_company_tech_stack.
search_companies_by_technology (beta)
Beta, early access: book a call to join. An account without access gets a 404 from the tool. Takes the filters of company search by technology: company_technology_slug_or, company_technology_slug_and, company_technology_slug_not, expand_technology_slugs, tier_min, min_num_jobs_found, company_country_code_or, company_domain_or, company_name_or, min_employee_count, max_employee_count, order_by, limit, cursor and include_total_results, and returns the same metadata and data. Each company's technologies_found carries the evidence: tier (confirmed, likely, mentioned), posting counts over 7, 30 and 180 days, and the first and last date a posting named it.
get_company_tech_stack (beta)
| Parameter | Type | Description |
|---|---|---|
company | string | Required. A domain ("stripe.com") or a company name. |
tier_min | string | confirmed, likely (default) or mentioned. |
kind | string | Comma-separated kinds to keep, e.g. "product,certification". |
Returns the response of company tech stack: as_of, company, num_jobs, num_technologies and data.
search_documentation
| Parameter | Type | Description |
|---|---|---|
query | string | Required, at least 2 characters. What to look up, e.g. "filter by salary" or "webhook signature". |
limit | number | Maximum sections to return, an integer from 1 to 20. Default 5. |
Returns the query and a results array of sections, each with title, heading, url, excerpt, and score.
list_pricing_plans
Takes no parameters. Returns currency (USD), an upgrade line, and a plans array with each plan's name, monthlyPriceUsd, monthlyJobs, maxResultsPerRequest, and requestsPerSecond. Each package also carries a checkout_url that opens it on the billing page; upgrade_plan returns a one-click Stripe link instead.
get_account_info
Takes no parameters. Returns the account the connection is signed in to: user_id, email, name, auth_type (oauth or api_key), plan, the month the figures cover, monthly_credits, one_time_credits (Free only), credits_used, credits_remaining, extra_credits, max_results_per_request and requests_per_second. It costs no credits.
upgrade_plan
Returns a Stripe link where you buy a package, or confirm a move to a larger one. Your assistant is told to use it when you ask for more credits, when a tool answers that you are out of credits, or when a result carries low_balance, and to show you the link rather than claim anything was bought. The call itself charges nothing; the new monthly allowance applies within a minute of paying.
| Parameter | Type | Description |
|---|---|---|
package | string | builder, growth, scale or business. |
credits | number | Monthly credits wanted instead of a package name; the smallest package that covers them is chosen. |
With neither, it picks the package that covers 1.5x what you used this month, and never the package you already hold.
{
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_a1B2c3",
"flow": "checkout",
"package": "growth",
"credits": 100000,
"billing_interval": "month",
"price_usd": 149,
"note": "Open this link to pay with Stripe; the new allowance is live within a minute."
}
flow is checkout for a first package and portal when the link confirms a move to a larger package than the one you hold. The tool never changes your plan itself. A smaller package is refused with downgrade_requires_dashboard and a billing_url: downgrades are made on the billing page. More than 500,000 credits a month is an Enterprise plan: the tool answers an error with contact_url.
low_balance
search, fetch, search_jobs and the company technology tools report credits_remaining: in usage for search, as a string in metadata for fetch, and in metadata for the others. When fewer than 20% of your allowance are left - never fewer than 100 credits, and 200 of the free credits on Free - the result also carries a low_balance block:
{
"low_balance": {
"remaining": 150,
"message": "This JobsPipe account has 150 credits left of 1,000. Tell the user; if they want more, call upgrade_plan and show them the Stripe link it returns.",
"upgrade": "call upgrade_plan"
}
}
It is only a notice: the search itself worked. The fields are additive, so a client that ignores them sees the results it always did.
Limits and errors
The MCP server inherits your plan's limits from the REST API. Only authentication failures come back as an HTTP status. Everything that happens inside a tool — including an exhausted quota or a rate limit — comes back as a normal tool result with isError: true and a JSON body such as { "error": "Monthly request quota exceeded" }, so the agent can read it and react.
| Where | What you get | Meaning |
|---|---|---|
HTTP 401 | Connect with OAuth, or send a JobsPipe API key (jp_live_) ..., with signup_url and docs_url | No Authorization: Bearer or x-api-key credential was sent. |
HTTP 401 | Access token is invalid or expired. Reconnect to continue. | The bearer is not a jp_live_ key and not a live OAuth token. |
Tool result, isError | { "error": "Invalid API key" } | The jp_live_ key was not accepted by the API. |
Tool result, isError | { "error": "Monthly request quota exceeded", "message": "...", "upgrade_url": "...", "next_step": "..." } | Your plan's monthly job quota is used up. next_step tells the assistant to call upgrade_plan for a direct Stripe checkout link; upgrade_url opens billing with a package sized to your use; saved signals keep working. |
Tool result, isError | { "error": "Rate limit exceeded" } | Per-second rate limit exceeded twice in a row. The server already waited and retried once, so slow down before calling again. |
Call get_account_info to see your own plan, limits and remaining credits, and list_pricing_plans to see the quota, rate limit, and max results per call for each plan.
Other agent surfaces
Beyond MCP, JobsPipe is discoverable by autonomous agents:
- Agent skills —
SKILL.mddocuments for autonomous discovery athttps://jobspipe.dev/.well-known/agent-skills/index.json. llms.txt— an agent-friendly index of the site athttps://jobspipe.dev/llms.txt. Every content page is also available as raw Markdown.- OpenAPI spec — the API's machine-readable schema at
https://jobspipe.dev/openapi.json. To try requests interactively, use the API Explorer in these docs or the reference athttps://api.jobspipe.dev/docs/reference.
[
Connect JobsPipe to your assistant
Step-by-step setup for every AI assistant that can talk to JobsPipe — Claude, ChatGPT, Gemini, Grok, Perplexity, Le Chat, Cursor and the coding agents — from the directory listing or as a custom MCP connector.