Skip to content

Getting Started

This guide walks through the typical pay-per-use workflow — the same loop a daily job-search notebook runs.

Prefer chat? Connect Resumly to a supported MCP client and run this same loop in plain English.

1. Install and Authenticate

pip install resumly
from resumly import Resumly

client = Resumly(api_key="rly_your_api_key")  # or set RESUMLY_API_KEY

client.balance()      # {'balance_usd': 5.0, ...} — first key includes $5 trial credit
client.readiness()    # what's missing before you can auto-apply

2. Upload Your Base Resume — $0.05

client.upload_base_resume("my_resume.pdf")
base = client.base_resume()

3. Build a Search from Plain English (free)

draft = client.interpret(
    "remote senior data engineer roles in the US or Canada, posted in the last week"
)
print(draft["assumptions"])       # what the interpreter assumed
print(draft["draft_patch"])       # validated structured filters

# How many jobs would this shape match? Free, cached ~10 minutes.
print(client.supply_check(**draft["draft_patch"]))

4. Save the Search and Refresh It — $0.05 per refresh

q = client.create_query("Data Eng — remote", **draft["draft_patch"])
client.refresh(q["query_id"])     # 202 — jobs land already match-scored

# Later, decide whether another nickel is worth it:
for query in client.queries()["queries"]:
    print(query["query_name"], query["last_refreshed_at"], query["new_jobs_last_run"])

5. Read Your Board (free)

jobs = client.jobs(min_match_score=0.7, auto_apply=True, sort_by="match")
for job in jobs["jobs"][:5]:
    print(f"{job['match_score']:.0%}  {job['job_title']} @ {job['organization']}")

best = jobs["jobs"][0]
client.save_job(best["job_id"])

# Or bring your own posting — $0.05:
imported = client.import_job("https://example.com/careers/senior-engineer")

6. Tailor a Resume — $0.25

resume = client.tailor(best["job_id"])          # for a board job
# resume = client.tailor(url="https://...")     # or an outside URL
# resume = client.tailor(description="...")     # or raw text

client.download(resume["resume_id"], path="tailored.docx")   # free DOCX
print(client.comparison(resume["resume_id"]))                # base vs tailored

7. Cover Letter and PDF — $0.10 + $0.02

client.cover_letter(resume["resume_id"])

op = client.export_pdf(resume["resume_id"], document_type="resume")
final = client.wait_operation(resume["resume_id"], op["operation_id"])

8. Interview Prep — $0.10 / $0.05 / $0.25

client.interview_questions(best["job_id"])                    # $0.10
qs = client.get_interview_questions(best["job_id"])

feedback = client.interview_answer(                           # $0.05
    best["job_id"], question_index=0,
    answer="I led the migration of our monolith to microservices...",
)

client.company_research(best["job_id"])                       # $0.25

9. Apply — $0.50, billed only on success

eligible = client.eligible_jobs(min_match_score=0.7)

queued = client.apply(best["job_id"], resume["resume_id"])
# $0.50 is reserved now and billed only when the submission is confirmed.

print(client.applications(status="applied"))
print(client.application_stats())

10. Autopilot and Inbox

# Hands-free: search, tailor and apply on a schedule (same per-op pricing)
client.enable_autopilot(job_queries=[q["query_id"]], min_match_score=70)
print(client.autopilot_dashboard())

# Employer replies land in your managed inbox, auto-classified
invites = client.inbox_emails(category="interview_invitation")
client.reply_email(invites["emails"][0]["id"], body_text="Thursday 2pm works great.")

Topping Up

url = client.buy_credit("25")   # "10" | "25" | "50" | "100" — returns a Stripe checkout URL
print("Complete the top-up here:", url)

Error Handling

from resumly import (
    ResumlyError, AuthenticationError, RateLimitError,
    InsufficientCreditError, DuplicateRequestError,
)

try:
    client.tailor(url="https://example.com/job")
except InsufficientCreditError as e:
    print(f"Balance ${e.balance_usd} can't cover ${e.price_usd} — top up first")
except DuplicateRequestError:
    print("Already ran with this idempotency key — not charged twice")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except ResumlyError as e:
    print(f"API error: {e}")

Retries on metered POSTs are safe when you pass your own key:

client.tailor(best["job_id"], idempotency_key="tailor-2026-08-31-acme-senior-eng")