PasteApply
MCP remoto de HTTP Streamable para adaptar honestamente currículums y cartas de presentación. Generación gratuita; desbloqueo con Stripe para descargas. MCP: https://pasteapply.com/mcp
Documentación
PasteApply for agents
Product: https://pasteapply.com
MCP happy path (bots: copy this loop)
Connect Remote MCP https://pasteapply.com/mcp (Streamable HTTP). Tools: status, store_resume, generate, unlock_link, confirm_payment, get_generation. Prefer MCP over HTTP twins and over driving the website.
- status — readiness + prices. Confirm Stripe/OpenAI are up.
1b. store_resume(
resume) once (optional) →resumeIdrs_1.<uuid>— on later generate, passresumeId(preferresumeIdover re-pasting the full resume; pastedresumestill wins if both are sent). TTL ~30 days. File uploads: HTTPPOST /api/resumesmultipartfile(pdf/docx/doc/txt/md, max ~8MB). - generate(
jobPostingorjobUrl,resumeorresumeId) — seconds later: teaser only whenpaid:false—coverage/ gaps,fit(recommend: skip|weak|apply +mustHavesMissing/impliedOnly),recommendUnlock(false whenfit.recommendisskip; true forweak|apply; optionalunlockAdvicewhen false),proof(bulletsRephrased=== visible original→tailored pair count),whatChanged/diffpairs, redacted sample.files.readyis false — nofiles.pdf/files.docx/files.coverPdf. No full letter, no full resume. Prefer pastedresumeif bothresumeandresumeIdare sent. - Check
recommendUnlock/fit.recommendbefore unlock. IfrecommendUnlockis false (orfit.recommendisskip), do not callunlock_link— missing must-haves cannot be invented. Otherwise unlock_link(price) — Stripe Checkout URL → hand to a HUMAN only. Never scrape Checkout. Prices:monthly=$3/1 ·export=$5/5 (default) ·pro=$10/50+Pro ·power=$20/200 unlocks+Pro extras. - poll get_generation(
id) untilpaid:true— the Stripe webhook marks the draft paid and persists it; nocs_ritual required on the happy path. Use confirm_payment(cs_session id) only if the poll stays unpaid (slow webhook). - When
paid:true: fullcoverLetter, structured resume JSON, and paidfiles—files.ready,files.pdf,files.docx,files.coverPdf,files.expiresAt,files.coverLetter,files.resume.files.pdf/files.docx/files.coverPdfare absolute?dl=URLs that last the UTC billing month (expire at UTC end of the current calendar month). Refresh anytime in-month viaget_generation/ confirm / paid generate (same generation still paid). If a file GET returns 403 (month ended), re-callget_generationfor a fresh token (may 404/402 if the paid generation itself is past month-end TTL). Download Content-Disposition is LastCompany_Role.pdf / .docx and LastCompany_Role_cover.pdf (posting company+role, else latest experience; fallback Resume.pdf / Cover_Letter.pdf). files.coverPdf is /api/pdf/:id?kind=cover&dl=; alias GET /api/cover-pdf/:id.
Paid files object (machine shape):
{
"ready": true,
"pdf": "https://pasteapply.com/api/pdf/<id>?dl=<token>",
"docx": "https://pasteapply.com/api/docx/<id>?dl=<token>",
"coverPdf": "https://pasteapply.com/api/pdf/<id>?kind=cover&dl=<token>",
"expiresAt": "2026-10-01T00:00:00.000Z",
"coverLetter": "…full letter…",
"resume": { "schemaVersion": 1, "name": "…", "contact": { "email": "…" }, "summary": "…", "skills": [], "experience": [{ "employer": "…", "title": "…", "start": "…", "end": "…", "location": "", "bullets": [] }], "education": [] }
}
Paid resume JSON: ATS schema v1 — schemaVersion:1, structured contact {email?, phone?, location?, links?} (not one blob), and every experience row has required employer|title|start|end|location|bullets (location "" if unknown; dates/employers frozen from source).
Honesty: never invent employers/dates/skills/metrics. If the posting wants something not on the resume, omit it. Guarantee: https://pasteapply.com/honesty · https://pasteapply.com/honesty.md
Quotas (UTC calendar month): $3/mo = 1 unlock · $5/mo Export = 5 unlocks · $10/mo Pro = 50 unlocks + hire-fit / variants · $20/mo = 200 unlocks + same Pro extras. No public promo codes.
Site ad unlock (humans only — AppLixir S2S): The website may offer Unlock free with ads after an unpaid generate when recommendUnlock is true (not skip), when AD_UNLOCK_ENABLED + AppLixir API key are live. The human watches AppLixir rewarded ads (SDK v6.1.0); client complete is optimistic UI only. The server grants only after AppLixir S2S GET /api/ad/applixir (MD5+TID signature, dedupe on tid) or generic HMAC POST /api/ad/reward, then marks that generation paid:true with the same artifacts as Stripe. MCP / bots / Bearer pa_… cannot ad-unlock — stay on Stripe: unlock_link → poll get_generation. No client-trust / AdSense grants; no LLM call on unlock.
Paid bots: Authorization: Bearer pa_… from confirm_payment / POST /api/confirm (apiKey) or POST /api/agent-key { "sessionId": "cs_…" } (confirm is fallback when you need an apiKey or the webhook is slow). Status/generate return unlocksUsed, unlocksRemaining, period (YYYY-MM); at cap generate → 402 code:"unlock_quota" with remaining:0 / unlocksRemaining:0 (not retry-later); free daily cap → 429 code:"free_cap"; bogus Bearer → 401 (no silent teaser).
Cursor mcp.json (hosted default — no provider key):
{
"mcpServers": {
"pasteapply": {
"url": "https://pasteapply.com/mcp"
}
}
}
Heavy agents can BYOK through a secret connection header, never a tool argument or chat paste. Use an env-var placeholder — never a literal key. Rotate any key previously pasted into tool args or chat.
{
"mcpServers": {
"pasteapply": {
"url": "https://pasteapply.com/mcp",
"headers": {
"X-PasteApply-BYOK-Key": "${OPENAI_API_KEY}"
}
}
}
}
Some Cursor builds want "type": "http" next to the URL. Claude / other connectors: paste https://pasteapply.com/mcp as the remote MCP URL. Referral ?ref=CODE works on the site (see Referral); the MCP URL itself does not need ?ref=.
HTTP twins (secondary)
Base: https://pasteapply.com — status/generate/checkout work without auth (teaser). Paid bots use Authorization: Bearer pa_…. Same contract as MCP.
GET /.well-known/mcp.json→ directory listing (name,mcp,docs,honesty)GET /api→ machine index (paths + prices)GET /api/status→ readiness JSON (openaiConfigured,stripeConfigured,googleAuthConfigured,demoMode,prices,byokSupported,byokProviders; with Bearer pa_… alsoplan/unlocks*/period/agentHints)POST /api/resumes{ "resume": "..." }or multipartfile(pdf/docx/doc/txt/md, max ~8MB) →{ "resumeId": "rs_1.<uuid>" }. Stores extracted text only (~30 day TTL). Cap ~20 stores/hour/IP. No listing of stored resumes.POST /api/generate{ "jobPosting", "resume" }or{ "jobUrl", "resumeId" }or{ "useSample": true }→generation.id+ truncated teaser whenpaid:false. If bothjobPostingandjobUrl, prefer pastedjobPosting. If bothresumeandresumeId, prefer pastedresume. Unknown/expiredresumeId→400.jobUrlmust be public http(s); bad/blocked/failed fetch →400(not 500). Full bodies only after unlock. OptionalproPassesfor Pro. Optional BYOK:modelProvider(hosteddefault |openai|anthropic) in JSON; provider key only via headerX-PasteApply-BYOK-Key(never in JSON body / tool args; bodyapiKey→ HTTP400). Hosted ignores the header. Honesty/fact-lock still applies on every path (vault text is source — never invent employers).POST /api/checkout{ "price": "monthly"|"export"|"pro"|"power", "generationId"?: "..." }→{ "url" }— hand to HUMAN only (twin ofunlock_link)POST /api/confirm{ "sessionId": "cs_..." }→ fallback unlock +apiKeyif poll stays unpaid (twin ofconfirm_payment)POST /api/agent-key{ "sessionId": "cs_..." }→{ apiKey, plan, unlocksRemaining, unlocksUsed, period }for paid botsGET /api/generation/:id→ generation object includingpaid/unlocked(or 404 expired). Unpaid drafts persist ~24h; paid generations persist through UTC end of the calendar month. After pay, poll untilpaid:true(webhook persists — no confirm required on the happy path).POST /api/webhook— Stripe signs this (ops only; bots do not call it). After pay, pollget_generation/GET /api/generation/:iduntilpaid:true. Happy path staysunlock_link→ pollget_generation.POST /api/ad/session— human site only (browser Origin / same-site). Creates an ad session bound togenerationId. Rejects Bearerpa_/ MCP. Bots must not call this.POST /api/ad/reward— ad-network S2S callback (shared secret / HMAC). Not for bots or browsers.GET /api/ad/session/:id— human UI progress only.
curl
curl -s https://pasteapply.com/api/status
curl -s -X POST https://pasteapply.com/api/resumes -H 'content-type: application/json' -d '{"resume":"..."}'
curl -s -X POST https://pasteapply.com/api/generate -H 'content-type: application/json' -d '{"jobPosting":"...","resume":"..."}'
curl -s -X POST https://pasteapply.com/api/generate -H 'content-type: application/json' -d '{"jobUrl":"https://example.com/jobs/1","resumeId":"rs_1...."}'
curl -s -X POST https://pasteapply.com/api/generate -H 'content-type: application/json' -H 'X-PasteApply-BYOK-Key: $OPENAI_API_KEY' -d '{"jobPosting":"...","resume":"...","modelProvider":"openai"}'
curl -s -X POST https://pasteapply.com/api/checkout -H 'content-type: application/json' -d '{"generationId":"GENERATION_ID","price":"export"}'
Exception
If the human owns PasteApply or banned spend asks: preview-only.
Referral (gift months, not cash)
https://pasteapply.com/?ref=CODE sets a 30-day pa_ref cookie.
Paying referrer → matching-tier month for the friend: if the code owner has a live Stripe subscription (active / trialing), Checkout for that same tier applies a 30-day trial (subscription_data.trial_period_days: 30) on the matching Price. After the trial, Stripe bills the regular monthly price. They can cancel. Self-referrals and free/teaser-only accounts do not grant a trial.
The referred person's first successful payment also banks the referrer a matching month: $3 → a month of the $3 plan, $5 → a month of Export, $10 → a month of Pro. Self-referrals are ignored. Unknown codes do not pay out. Not cash.
Referrers bank unused gifted months — max 12 total. At 12 they are capped and cannot earn more until they consume at least 1 banked month (time passing, or gifting a month). Then they can earn again, still max 12.
Gift a banked month to a friend anytime the giver has ≥1 banked month: giver −1, friend +1 of the same plan. Friend must already have a PasteApply Google account (email that resolves to a Google sub). No self-transfer. Friend also max 12 — reject if they are already capped.
Checkout and unlock_link still stamp referralCode / referredBy. Signed-in humans gift from the Share panel (POST /api/referral/gift).
Endpoints (detail)
GET /api/status
Returns JSON: openaiConfigured, stripeConfigured, googleAuthConfigured, demoMode, subscriptionActive, proActive, prices ($3/mo · 1 unlock, $5/mo · 5 unlocks, $10/mo Pro · 50 unlocks, $20/mo · 200 unlocks), byokSupported, byokProviders (openai, anthropic). With Authorization: Bearer pa_…: also plan, unlocksUsed, unlocksRemaining, period (YYYY-MM), agentHints (e.g. reuse resumeId — don't re-paste the full resume). Bogus Bearer → 401. No secrets. CORS * (no credentials).
POST /api/resumes
Content-Type: application/json or multipart/form-data
{ "resume": "Jane Doe\nAcme 2020-2024 Operations Analyst\nSQL" }
Or multipart field file (.pdf / .docx / .doc / .txt / .md, max ~8MB) — we extract text via the same path as /api/extract-resume and store text only (no polish / no invented employers).
Response: { "resumeId": "rs_1.<uuid>" }. Reuse resumeId on generate. Soft TTL ~30 days (unknown/expired → generate 400). Cap ~20 stores/hour/IP. ResumeId is a capability (unguessable) — there is no list endpoint.
POST /api/generate
Content-Type: application/json
{
"jobPosting": "...",
"jobUrl": "https://example.com/jobs/1",
"resume": "...",
"resumeId": "rs_1.<uuid>",
"useSample": false,
"proPasses": [],
"modelProvider": "hosted",
"model": ""
}
Header (BYOK only): X-PasteApply-BYOK-Key: ${OPENAI_API_KEY} — env-var placeholder, never a literal key in docs or chat.
useSample: true loads the public demo. Identical job+resume (same fingerprint) returns the same generation.id and debits at most one unlock / free daily consume. proPasses only applies for Pro members. Pass jobPosting text and/or jobUrl (public http(s) only; we fetch and strip HTML; prefer pasted jobPosting if both). Pass resume text and/or resumeId from POST /api/resumes / MCP store_resume (prefer pasted resume if both). Unknown/expired resumeId → HTTP 400. Bad/blocked URL, fetch failure, empty/non-HTML body → HTTP 400 with a clear message (not 500). Counts toward the free daily generate cap like a pasted job.
Optional BYOK (server-side only): hosted is default. Heavy agents set modelProvider to openai or anthropic and send the provider key only in X-PasteApply-BYOK-Key (HTTP header / MCP connection headers). Never place provider keys in tool arguments, JSON body, or chat. JSON body apiKey is rejected (HTTP 400). Hosted ignores the header and never lets the client override our model (cost lock). BYOK uses the user's tokens and may set model. The key is never logged, persisted, hashed, echoed, or returned. Rotate a key if it was previously pasted. Fact-lock still runs on every path and fails closed (HTTP 422). Pricing ladder unchanged ($3=1 / $5=5 / $10=50 / $20=200).
Free vs paid payload
Unpaid (paid: false, unlocked: false, preview: true): incomplete teaser only. Keep coverage, fit, recommendUnlock (and unlockAdvice when skip), proof / factLock (bulletsRephrased = visible pair count — equals the number of original→tailored pairs shown), and whatChanged / diff with every original → tailored pair (original is the source line; tailored may be shortened). Redacted sample (coverLetter is the first sentence with [redacted]; resume keeps name + first role header + 1–2 redacted bullets). No full cover letter, no full tailored resume, no snapshot. Unpaid files.ready is false — no files.pdf / files.docx / files.coverPdf.
Paid (paid: true, unlocked: true, preview: false) after Checkout confirm or an active plan still under its monthly unlock quota: full coverLetter, full resume, clean files, and snapshot. Paid files: files.ready (true), files.pdf, files.docx, files.coverPdf, files.expiresAt, files.coverLetter, files.resume. Download tokens last the UTC billing month (HMAC ?dl= expires at UTC end of the calendar month); refresh via get_generation anytime in-month. Paid generation Blob TTL also extends to UTC month end (unpaid teasers stay ~24h). On file GET 403, re-call MCP get_generation / GET /api/generation/:id for a fresh token. Unpaid keeps files.ready false and omits pdf/docx/coverPdf. Pro extras (hire-fit, variants, interview, scorecard) on $10 Pro or $20/mo (200 unlocks).
MCP generate is the same contract. Do not stitch the teaser into a file. Check recommendUnlock / fit.recommend first — if skip / recommendUnlock:false, do not unlock. Otherwise call MCP unlock_link (default $5 Export = 5 unlocks/mo) and hand the URL to a human.
Stable fields: generation.id, coverLetter, resume, coverage, fit (always: recommend skip|weak|apply, mustHavesMissing, impliedOnly — gaps only, never invents), recommendUnlock (false when fit.recommend===skip; true for weak|apply; optional unlockAdvice when false), whatChanged (same as diff), proof, factLock (human line), quality, paid, unlocked, preview, estimate (~10s), files. CORS * (no credentials). Prefer server-side fetch.
Machine-readable honesty (also on MCP generate and GET /api/generation/:id):
{
"proof": {
"factLock": true,
"inventedClaims": 0,
"bulletsRephrased": 4,
"skillsLed": 2,
"promise": "We only rearrange what is already true."
},
"coverage": [
{ "skill": "SQL", "status": "present", "note": "Already on the source resume. We can lead with this." },
{ "skill": "Salesforce admin", "status": "implied", "note": "Salesforce is on the resume; the exact admin title is not." },
{ "skill": "Kubernetes", "status": "missing", "note": "Kubernetes is not on the resume. Lead with SQL — do not invent Kubernetes." }
]
}
coverage[].status is present | implied | missing. Every generate also returns fit: deterministic skip|weak|apply from coverage (any missing must-have ⇒ skip; honesty still strips invented skills separately). Agent views also include recommendUnlock (false on skip; true on weak|apply) and optional unlockAdvice when false — check before unlock_link. Present = named on the source resume. Implied = a related tool or title is on the resume, not the exact word. Missing = not on the resume — we show the gap and do not invent it. If inventedClaims would be >0, generate fails (HTTP 422) instead of returning fiction. Guarantee: https://pasteapply.com/honesty
POST /api/checkout
{ "price": "export", "generationId": "<uuid>" }
price: monthly | export | pro | power — required. $3/mo = 1 unlock (price: "monthly"). $5/mo = 5 unlocks (price: "export", default). $10/mo Pro = 50 unlocks + extras (price: "pro"). $20/mo = 200 unlocks + same Pro extras (price: "power"). Empty body, missing price, or bogus price → HTTP 400. generationId optional. Website checkout type for $20 is power ($20=200).
Response: { "url": "https://checkout.stripe.com/..." } — give this link to a human.
POST /api/confirm
{ "sessionId": "cs_live_..." }
Fallback unlock after Checkout when get_generation still shows paid:false (slow webhook). On success includes apiKey (pa_1.…) for paid-bot Bearer auth. Twin: POST /api/agent-key with the same sessionId. Happy path: poll GET /api/generation/:id / MCP get_generation until paid:true — no cs_ required.
GET /api/generation/:id
Returns the generation object plus paid / unlocked, proof, and coverage, or 404 if expired or unknown. Unpaid drafts persist ~24h; paid generations persist through UTC end of the created calendar month. Poll this (or MCP get_generation) right after generate; you should get the teaser, not 404. Unpaid responses stay teasers. After the human pays, the Stripe webhook marks the draft paid and persists it — poll until paid: true for the full payload (confirm only if the poll stays unpaid).
Rate limits
Public teaser path needs no key. Paid bots use Authorization: Bearer pa_…. Free teasers are capped per IP and per browser session (UTC day). Paid plans are capped by UTC calendar-month unlocks: $3 = 1, $5 = 5, $10 = 50, $20 = 200. At cap, generate returns HTTP 402 with a human upsell message.
- Real job+resume teasers: 15 / day
- Sample (
useSample: true): 60 / day
Exceed free cap: HTTP 429, Retry-After (seconds until the next UTC midnight), and JSON:
{ "error": true, "code": "free_cap", "message": "Daily free generate limit reached (15 real job+resume teasers per UTC day). Try again in about 12h (retryAfter seconds until UTC midnight). Or unlock with $3/mo (1 unlock), $5 Export (5), $10 Pro (50), or $20/mo (200) this calendar month (UTC).", "retryAfter": 43200 }
Show message to the human. Honor retryAfter / Retry-After. If you get 429, 502, or 504, wait (or backoff 1s, 2s, 4s on 502/504). Do not hammer generate. Prefer one generate at a time per client.
Paid unlock month cap (not retry-later): HTTP 402 and JSON:
{ "error": true, "code": "unlock_quota", "message": "…", "remaining": 0, "unlocksRemaining": 0, "unlocksUsed": 1, "period": "2026-09", "plan": "starter" }
Do not treat 402 / unlock_quota as wait-and-retry — upsell or stop.
Errors and retries
JSON uses human-readable message (or error string on checkout/confirm) — show that text to the human; do not invent status copy.
400— bad JSON, empty job/resume (unlessuseSampleor validresumeId), unknown/expiredresumeId, bad/blockedjobUrl/ fetch fail, or confirm with a bogus/unknown CheckoutsessionId(not 502). Example: "Paste a job posting (or jobUrl) and your current resume." / "Unknown or expired resumeId…" / "That job URL is blocked (local or private network). Paste the posting text instead." / "That Checkout session was not found."422— honesty lock: invented claims. Fix the source resume; do not retry the same invention.429— free generate daily cap (code:"free_cap"; 15 real / 60 sample per UTC day). Wait forretryAfter/Retry-After(seconds until UTC midnight). Never treat as a paid-wall.403— download?dl=token expired (UTC billing month ended). Re-call MCPget_generation/ GET/api/generation/:idfor a freshfiles.pdf/files.docx/files.coverPdfURL; do not DIY the file.402— unpaid PDF/DOCX/cover download (code:"payment_required"), or paid plan at monthly unlock cap (code:"unlock_quota",remaining:0/unlocksRemaining:0, plus used/period/plan; LIVE-UNVERIFIED). Showmessage. Upsell the next tier; do not retry-later (402 ≠ 429). Do not invent a promo code.404— generation expired (unpaid ~24h; paid UTC month end) or unknown id504— generate took too long; try again in a moment- Standard generate aborts at ~20s; Pro rewrite ~28s.
estimateis~10s.
Input size
Job posting max 20,000 characters. Resume max 40,000 characters. Longer bodies return 400. On the $20 plan, hosted Pro accepts at most 46,380 characters of resume + job posting combined (Standard and BYOK keep the full limits).
MCP tool schemas
Remote MCP https://pasteapply.com/mcp (Streamable HTTP). Tools:
status— no args. Readiness + prices (+ plan/unlocks when inboundAuthorization: Bearer pa_…).store_resume—resumetext →{ resumeId }(rs_1.<uuid>). Store once; preferresumeIdon later generate over re-pasting. Files: HTTP multipartPOST /api/resumes(not this tool).generate—jobPosting?,jobUrl?,resume?,resumeId?,useSample?,proPasses?, optional BYOKmodelProvider?/model?(noapiKeyin tool args). Provider key only via connection headerX-PasteApply-BYOK-Key. PreferresumeIdover re-pasting afterstore_resume; pastedresume/jobPostingstill win if both pairs sent. Forwards inbound Bearer + BYOK header. Unpaid: teaser JSON + proof +recommendUnlock/fit(not the full letter). Paid key with remaining>0: full cover + resume + files (consumes 1 unlock). CheckrecommendUnlockbeforeunlock_link.unlock_link— only whenrecommendUnlockis true (skip whenfit.recommendisskip).generationId?,price?(monthly= $3/mo · 1 unlock |export= $5/mo · 5 unlocks, default |pro= $10/mo · 50 unlocks + Pro |power= $20/mo · 200 unlocks + Pro extras). Stripe Checkout URL for a human. After pay, pollget_generationuntilpaid:truefor paidfiles.*(ready,pdf,docx,coverPdf,expiresAt,coverLetter,resume).confirm_paymentonly if the poll stays unpaid.confirm_payment—sessionId(cs_...). Fallback when webhook is slow. After success, payload is the full artifact + paidfiles+apiKey.get_generation—idorgenerationId(same uuid). Happy path after pay: poll untilpaid:true. Teaser until paid. When paid, returns full cover + resume +files(ready,pdf,docx,coverPdf,expiresAt,coverLetter,resume) with tokens lasting the UTC billing month. On file 403, re-call for a fresh token.
Browser GET Accept: text/html on /mcp returns a short hint page linking here. MCP clients should send JSON / event-stream, not HTML.
Jobs board (humans)
Human site surface: /jobs (search), /jobs/{id} (detail), /jobs/employers (post/manage). Owned PasteApply listings always. Outside results (qualifying paid plans only: $5 Export 20 searches/mo, $10 Pro and $20 75/mo per account, UTC month): GET /api/jobs?q=…&location=…&web=1 with a signed-in paid session runs the OpenAI Responses web_search tool restricted to public ATS job boards (Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Jobvite, iCIMS and similar); every URL is re-validated server-side and only real source/citation listing URLs are returned (source:"web", external:true, applyUrl). Cached by normalized query+location (cache hits don't count). Outside results are link-checked before they are shown and cached for up to 12h, so a listing can still close within that window. Free/$3 → owned posts + one upsell line (webSearch.upsell). No competitor scraping: never Indeed/LinkedIn/Glassdoor/Google scrapes, no SerpAPI. Interview % for an outside result: POST /api/jobs/{ws_id}/interview-chance with {resume, jobUrl: applyUrl}, or tailor via /?jobUrl={applyUrl} for before/after. Free posts: signed-in Google employers draft → Publish (POST /api/jobs/{id}/publish) marks open + expiresAt = now+30d — no Stripe, no 402. Rate-limited per employer UTC day; open-seat cap still applies. Optional Promote: $30 one-time for 30 days featured (env STRIPE_PRICE_JOB_PROMOTE=price_1UILnOH8RUFIVCIaPHLm1gMa, lookup pasteapply_job_promote_30d_30). Checkout metadata.type=job_promote → webhook sets promotedUntil; public board sorts featured first + badge. Unpromoted open jobs still listed. MCP stays tailor tools (status / store_resume / generate / unlock_link / …). Do not invent a search_jobs MCP tool until a real inventory API exists. Bridge: job detail → /?jobUrl=https://pasteapply.com/jobs/{id} (generate resolves owned URLs from Blob; honesty lock + unlock ladder unchanged).
Version
Contract v0. Changelog: site ad-funded unlock (humans only; bots stay Stripe); BYOK provider keys move to X-PasteApply-BYOK-Key (never tool args / JSON body); public $20 plan id is power ($20=200); resume vault store_resume / POST /api/resumes → resumeId rs_1.<uuid> (~30d); generate accepts resumeId; paid-bot Authorization: Bearer pa_… (confirm / /api/agent-key); UTC-month unlock quotas $3=1 / $5=5 / $10=50 / $20=200 + Pro extras; at cap generate is 402 + unlock_quota; free daily cap is 429 + free_cap; starter unlock is $3/month (price: "monthly"); default human unlock remains $5 Export; free generate daily caps (15 real / 60 sample) return 429; unpaid generate is a truncated teaser; paid unlock returns the full artifact + clean files; honesty proof + coverage (present / implied / missing); referral gift months (max 12); Drive save; remote MCP happy path status → optional store_resume → generate → unlock_link → poll get_generation until paid:true (confirm_payment fallback only). No public promo codes.
Rules for operators
- Prefer MCP (
https://pasteapply.com/mcp) over HTTP twins; prefer either over driving the website. - Never invent employers, dates, skills, or metrics.
- Never complete Stripe Checkout as the user.
- Never call
/api/ad/*as a bot — site ad unlock is humans only; bots stay Stripe paid. - Site for humans: https://pasteapply.com