Metadata-Version: 2.4
Name: resumly
Version: 1.0.0
Summary: Official Python SDK for the Resumly public API — pay-per-use job search, resume tailoring, and auto-apply. No subscription required.
Author-email: Resumly <support@resumly.ai>
License-Expression: MIT
Project-URL: Homepage, https://resumly.ai
Project-URL: Documentation, https://resumly.ai/docs
Project-URL: Repository, https://github.com/resumly/resumly-python
Project-URL: Issues, https://github.com/resumly/resumly-python/issues
Keywords: resume,ats,job-search,api,sdk,career,cover-letter,interview
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.28
Provides-Extra: docs
Requires-Dist: mkdocs>=1.6; extra == "docs"
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.27; extra == "docs"
Dynamic: license-file

# Resumly Python SDK

Official Python client for the [Resumly](https://resumly.ai) public API — pay-per-use job search, resume tailoring, and auto-apply. No subscription required: you prepay a wallet balance and each AI operation deducts its price. Reading data is always free.

```bash
pip install resumly
```

> ## ⚠️ 1.0 BREAKING CHANGE
>
> **The 0.x full-surface client is discontinued.** Versions ≤ 0.2.0 wrapped
> Resumly's entire internal application API (~249 methods). That internal
> surface is no longer published and those endpoints were never a stable
> contract. From 1.0.0 the `resumly` package is a thin client for the
> versioned public API at `/api/v1` only.
>
> - `from resumly import ResumlyClient` still works, but it now returns the
>   new public client (`Resumly`). Method names and endpoints have changed —
>   review the method table below before upgrading.
> - Pin `resumly<1.0` if you depend on the old client while you migrate.
> - Agency (B2B) integrations use `from resumly.agency import AgencyClient`.

## How billing works

- **$5 trial credit** comes with your first API key — on every plan, including free.
- You top up in fixed amounts ($10 / $25 / $50 / $100) via `buy_credit()`; the money sits in your API wallet.
- Every metered call deducts its price. **Your balance is your spend cap** — there is no way to owe money, no invoice surprise. When the balance can't cover an operation you get an HTTP 402 (`InsufficientCreditError`) and nothing runs.
- Failed operations are refunded automatically. Applications are special: $0.50 is *reserved* when you queue one and only billed when the submission is confirmed.
- Pass an `idempotency_key` to any metered POST to make retries safe — a reused key returns 409 (`DuplicateRequestError`) instead of charging twice.

### Rate card

| Operation | Price |
|---|---|
| Parse base resume (`upload_base_resume`) | $0.05 |
| Refresh a saved search (`refresh`) | $0.05 |
| Import an outside job (`import_job`) | $0.05 |
| Tailor a resume (`tailor`) | $0.25 |
| Cover letter (`cover_letter`) | $0.10 |
| Interview questions (`interview_questions`) | $0.10 |
| Answer feedback (`interview_answer`) | $0.05 |
| Translate a document (`translate`) | $0.10 |
| Export PDF (`export_pdf`) | $0.02 |
| Company research (`company_research`) | $0.25 |
| Rewrite base resume (`rewrite`) | $0.50 |
| Submitted application (`apply`) | $0.50 — reserved on queue, **billed only on confirmed submission** |

Everything else — listing jobs, reading resumes, downloading DOCX, stats, inbox, autopilot config — is free.

## Quickstart: the daily loop

Get an API key from [app.resumly.ai](https://app.resumly.ai) → Settings → API.

```python
from resumly import Resumly

client = Resumly(api_key="rly_...")   # or set RESUMLY_API_KEY

print(client.balance())               # {'balance_usd': 5.0, ...}

# 1. Describe what you want in plain English (free)
draft = client.interpret("remote senior data engineer roles in the US, posted this week")

# 2. Check supply before committing (free)
count = client.supply_check(**draft["draft_patch"])

# 3. Save the search and fetch jobs ($0.05 per refresh)
q = client.create_query("Data Eng — remote US", **draft["draft_patch"])
client.refresh(q["query_id"])

# 4. Read your scored board (free)
jobs = client.jobs(min_match_score=0.7, auto_apply=True, sort_by="match")
best = jobs["jobs"][0]

# 5. Tailor a resume for the best match ($0.25)
resume = client.tailor(best["job_id"])

# 6. Download it, or queue an auto-apply ($0.50, billed only on success)
client.download(resume["resume_id"], path="tailored.docx")
client.apply(best["job_id"], resume["resume_id"])

# 7. Track outcomes and employer replies (free)
print(client.applications(status="applied"))
print(client.inbox_emails(category="interview_invitation"))
```

## Method reference

| Area | Methods |
|---|---|
| **Account** | `balance()` · `usage()` · `keys()` · `create_key(name)` · `revoke_key(id)` · `buy_credit(amount)` → checkout URL |
| **Profile** | `upload_base_resume(path)` · `base_resume()` · `update_base_resume(data)` · `autofill_profile()` · `update_autofill_profile(attrs)` · `instructions()` · `set_instructions(text)` · `readiness()` |
| **Queries** | `queries()` · `query(id)` · `create_query(name, titles, **filters)` · `update_query(id, **fields)` · `delete_query(id)` · `supply_check(**filters)` · `interpret(prompt)` · `refresh(query_id)` |
| **Jobs** | `jobs(**filters)` · `job_stats()` · `job(id)` · `save_job(id)` · `block_job(id)` · `skip_job(id)` · `import_job(url, raw_text=None)` |
| **Documents** | `tailor(job_id \| url= \| description=)` · `resumes()` · `resume(id)` · `download(id, path=None)` · `comparison(id)` · `metadata(id)` · `delete_resume(id)` · `cover_letter(id)` · `get_cover_letter(id)` · `export_pdf(id)` · `operation(id, op_id)` · `wait_operation(id, op_id)` · `translate(id, language)` · `rewrite(instructions)` |
| **Prep** | `interview_questions(job_id)` · `get_interview_questions(job_id)` · `interview_answer(job_id, index, answer)` · `company_research(job_id)` · `get_company_research(job_id)` |
| **Applying** | `eligible_jobs()` · `apply(job_id, resume_id)` · `applications(status=None)` · `application_stats()` · `cancel_application(qid)` · `retry_application(qid)` |
| **Autopilot** | `autopilot()` · `enable_autopilot(**cfg)` · `update_autopilot(**cfg)` · `pause_autopilot()` · `resume_autopilot()` · `disable_autopilot()` · `autopilot_dashboard()` · `autopilot_activity()` · `autopilot_runs()` |
| **Inbox** | `inbox_status()` · `inbox_emails(**filters)` · `inbox_email(id)` · `reply_email(id, body_text=...)` · `compose_email(to=[...], subject=..., body_text=...)` |

## Errors

All exceptions inherit from `ResumlyError` and carry `status_code` and the raw `response` payload.

| Exception | HTTP | Meaning |
|---|---|---|
| `AuthenticationError` | 401 | Invalid or missing API key |
| `InsufficientCreditError` | 402 | Wallet balance below the operation price — carries `balance_usd`, `price_usd`, `operation`. Top up with `buy_credit()`. |
| `NotFoundError` | 404 | Resource does not exist |
| `DuplicateRequestError` | 409 | Idempotency-key reuse (not charged twice) or resource already exists |
| `ValidationError` | 422 | Request payload failed validation |
| `RateLimitError` | 429 | Rate limited — the client auto-retries honoring `Retry-After`; raised only when retries are exhausted |
| `ResumlyError` | other | Anything else |

```python
from resumly import Resumly, InsufficientCreditError

client = Resumly()
try:
    client.tailor(url="https://example.com/careers/123")
except InsufficientCreditError as e:
    print(f"Balance ${e.balance_usd} < price ${e.price_usd}")
    print("Top up:", client.buy_credit("25"))
```

## Agency (B2B) accounts

Agencies provision and manage client profiles with a full-scope agency key:

```python
from resumly.agency import AgencyClient
from resumly import Resumly

agency = AgencyClient(api_key="rly_agency_key")
profile = agency.create_client_profile("Jane Doe", "jane@example.com")

# Operate on the client's behalf with their own scoped key:
jane = Resumly(api_key=profile["api_key"])
```

## Links

- Dashboard & API keys: [app.resumly.ai](https://app.resumly.ai) → Settings → API
- Documentation: [resumly.ai/docs](https://resumly.ai/docs)
- Support: support@resumly.ai

## License

MIT
