# Resumly Python SDK

Official Python client for the [Resumly API](https://resumly.ai) — full programmatic access to every Resumly feature.

## Installation

```bash
pip install resumly
```

Or install from source:

```bash
cd sdk/
pip install -e .
```

## Quick Start

```python
from resumly import ResumlyClient

client = ResumlyClient(api_key="rly_your_api_key_here")

# Upload your base resume
client.upload_base_resume("my_resume.pdf")

# Create a tailored resume for a job posting
result = client.create_resume(job_url="https://example.com/jobs/12345")
job_id = result["job_id"]

# Get the final resume
resume = client.get_resume(job_id)

# Download as DOCX
client.download_resume(resume["resume_file_path"], path="tailored.docx")

# Run an ATS check on any resume file
ats = client.run_ats_check("my_resume.pdf")
print(ats["report"])
```

## Getting Your API Key

1. Log in to [resumly.ai](https://resumly.ai)
2. Go to **Settings > API Keys**
3. Click **Create New Key** and give it a name
4. Copy the key (shown only once)

## Complete API Reference

### Constructor

```python
ResumlyClient(
    api_key="rly_...",
    base_url="https://api.resumly.ai",  # override for local dev
    timeout=180,                          # seconds
    max_retries=3,                        # auto-retry on 429
)
```

---

### Base Resume

| Method | Description |
|--------|-------------|
| `upload_base_resume(file_path)` | Upload a PDF/DOCX as your base resume |
| `get_base_resume()` | Get the parsed JSON of your base resume |
| `update_base_resume(resume_data)` | Update base resume with new JSON |
| `improve_base_resume(comments)` | AI-improve base resume from feedback |
| `translate_base_resume(target_language)` | Translate base resume |
| `generate_base_resume_docx(resume_data)` | Generate Word doc from base resume |
| `freeze_base_resume_element(path, freeze)` | Freeze/unfreeze a resume element |
| `get_locked_elements()` | Get all locked elements |

### User Profile & Settings

| Method | Description |
|--------|-------------|
| `get_profile()` | Get user profile |
| `update_profile(email, **fields)` | Update profile fields |
| `upload_profile_picture(file_path)` | Upload profile picture |
| `get_user_settings()` | Get all settings |
| `update_user_settings(settings)` | Update settings |
| `reset_style_settings()` | Reset style to defaults |
| `add_always_show_skill(skill)` | Add to always-show skills |
| `remove_always_show_skill(skill)` | Remove from always-show skills |
| `add_never_show_skill(skill)` | Add to never-show skills |
| `remove_never_show_skill(skill)` | Remove from never-show skills |

### Job Preferences

| Method | Description |
|--------|-------------|
| `get_job_preferences()` | Get job preferences |
| `update_job_preferences(preferences)` | Update all preferences |
| `update_job_preference(key, value)` | Update single preference |
| `delete_job_preference(key)` | Delete a preference |

### Autofill & Custom Instructions

| Method | Description |
|--------|-------------|
| `get_autofill_attributes()` | Get autofill attributes |
| `set_autofill_attributes(attributes)` | Set autofill attributes |
| `get_custom_instructions()` | Get custom AI instructions |
| `set_custom_instructions(text)` | Set custom AI instructions |

### Memory

| Method | Description |
|--------|-------------|
| `get_memory()` | Get all memory points |
| `clear_memory()` | Clear all memory |
| `delete_memory_point(index)` | Delete specific memory point |

### Credits & Statistics

| Method | Description |
|--------|-------------|
| `check_credit(email)` | Check credit balance |
| `check_subscription(email)` | Check subscription status |
| `get_statistics(queue_statistics)` | Get user statistics |
| `get_credit_utilization()` | Credit utilization history |
| `get_ai_insights()` | AI insights for resume applications |
| `trigger_ai_insights()` | Recalculate AI insights |
| `get_ai_insights_jobs()` | AI insights for job database |
| `trigger_ai_insights_jobs()` | Recalculate job AI insights |
| `get_application_flow()` | Application flow Sankey data |
| `get_resume_timeline()` | Resume creation timeline |
| `get_resume_distribution()` | Resume distribution analytics |

---

### Tailored Resumes

| Method | Description |
|--------|-------------|
| `create_resume(job_url=..., job_description=..., cover_letter=False, interview_question=False)` | Create tailored resume (auto init+process) |
| `list_recent_resumes(days, favorites_only, queue_only, page, page_size)` | List recent resumes |
| `get_resume(job_id)` | Get final resume JSON |
| `update_resume(job_id, data)` | Update final resume |
| `get_resume_comparison(job_id)` | Base vs tailored comparison |
| `get_resume_metadata(job_id)` | Metadata (company, title, etc.) |
| `get_resume_insights(job_id)` | AI insights for resume |
| `regenerate_insights(job_id)` | Regenerate insights |
| `delete_resume(job_id)` | Delete resume |
| `download_resume(s3_url, path)` | Download DOCX file |
| `export_resume_pdf(job_id, document_type, path)` | Export as PDF |
| `regenerate_resume(job_id)` | Regenerate resume |
| `update_job_info(job_id, **fields)` | Update job info |

### Cover Letter

| Method | Description |
|--------|-------------|
| `create_cover_letter(job_id)` | Generate cover letter |
| `get_cover_letter(job_id)` | Get cover letter |
| `update_cover_letter(job_id, data)` | Update cover letter |
| `regenerate_cover_letter(job_id)` | Regenerate cover letter |

### Interview Questions

| Method | Description |
|--------|-------------|
| `create_interview_questions(job_id)` | Generate interview questions |
| `get_interview_questions(job_id)` | Get interview questions |
| `check_interview_answer(job_id, question, answer)` | Check an answer |

### Resume Actions

| Method | Description |
|--------|-------------|
| `rephrase_resume(job_id, text)` | Rephrase content |
| `translate_resume(job_id, target_language)` | Translate resume |
| `improve_resume(job_id, user_comment)` | AI-improve resume |
| `chat_with_resume(job_id, message)` | AI chat editing |
| `generate_bullet_points(**kwargs)` | Generate bullet points |
| `rephrase_text(text)` | Rephrase any text |

### Favorites & Queue

| Method | Description |
|--------|-------------|
| `favorite_resume(job_id)` | Toggle favorite |
| `unfavorite_resume(job_id)` | Remove favorite |
| `add_to_queue(job_id)` | Add to queue |
| `remove_from_queue(job_id)` | Remove from queue |
| `get_auto_apply_resumes()` | Get auto-apply eligible |

### Public Links (Share)

| Method | Description |
|--------|-------------|
| `create_public_link(job_id, **settings)` | Create shareable link |
| `get_public_link(job_id)` | Get link info |
| `update_public_link(job_id, **settings)` | Update link settings |
| `delete_public_link(job_id)` | Delete link |

### Company Research

| Method | Description |
|--------|-------------|
| `research_company(job_id)` | Trigger company research |
| `get_company_research(job_id)` | Get research results |

### Autofill & Feedback

| Method | Description |
|--------|-------------|
| `autofill_application(job_id, fields)` | Autofill job application |
| `submit_autofill_feedback(job_id, feedback)` | Submit autofill feedback |
| `save_change_summary(job_id, summary)` | Save change summary |
| `get_change_summaries(job_id)` | Get change summaries |
| `submit_feedback(job_id, feedback)` | Submit resume feedback |

### Email

| Method | Description |
|--------|-------------|
| `send_resume_email(job_id, **kwargs)` | Send resume via email |

---

### Batch Operations

| Method | Description |
|--------|-------------|
| `init_batch(job_urls, job_descriptions)` | Initialize batch |
| `process_batch(batch_id)` | Start processing |
| `get_batch(batch_id)` | Get batch status |
| `list_batches()` | List all batches |
| `get_batch_resumes(batch_id)` | Get resumes in batch |

---

### Job Search

| Method | Description |
|--------|-------------|
| `generate_job_search_query()` | Generate query from resume |
| `generate_and_run_search()` | Generate + run immediately |
| `run_job_search(query_id)` | Run a search |
| `get_search_queries()` | List all queries |
| `create_search_query(query)` | Create query |
| `update_search_query(query_id, query)` | Update query |
| `delete_search_query(query_id)` | Delete query |
| `get_relevant_jobs()` | Get relevant jobs |
| `get_job_status_stats()` | Status statistics |
| `get_job(job_id)` | Get job details |
| `get_related_jobs(job_id)` | Get related jobs |
| `save_job(job_id, save)` | Save/unsave job |
| `block_job(job_id, block)` | Block/unblock job |
| `skip_job(job_id, skip)` | Skip/unskip job |
| `reset_skipped_jobs()` | Reset all skipped |
| `embed_base_resume()` | Create resume embeddings |

### Job Search Agents

| Method | Description |
|--------|-------------|
| `create_search_agent(agent)` | Create agent |
| `update_search_agent(agent_id, agent)` | Update agent |
| `get_search_agents()` | List agents |
| `delete_search_agent(agent_id)` | Delete agent |
| `parse_and_store_job(raw_text)` | Parse raw job text |

### Job Match

| Method | Description |
|--------|-------------|
| `get_job_match_score(job_description, job_url)` | Get match score |

---

### Templates

| Method | Description |
|--------|-------------|
| `list_templates()` | List all template types |
| `list_standard_templates()` | List standard templates |
| `get_standard_template(id)` | Get standard template |
| `list_my_templates()` | List user template instances |
| `create_base_template(template_id)` | Create base template |
| `list_base_templates()` | List base templates |
| `get_base_template(id)` | Get base template |
| `update_base_template_html(id, html)` | Update template HTML |
| `reset_base_template(id)` | Reset template |
| `revise_base_template(id, feedback)` | AI revise template |
| `delete_base_template(id)` | Delete template |
| `create_resume_template(job_id, template_id)` | Create for resume |
| `list_resume_templates(job_id)` | List for resume |
| `get_resume_template(job_id, id)` | Get for resume |
| `update_resume_template_html(job_id, id, html)` | Update HTML |
| `reset_resume_template(job_id, id)` | Reset |
| `revise_resume_template(job_id, id, feedback)` | AI revise |
| `delete_resume_template(job_id, id)` | Delete |
| `render_template(job_id, template_id)` | Render template |
| `compress_content(job_id)` | Compress to fit |
| `convert_template_to_pdf(html)` | HTML to PDF |

---

### LinkedIn

| Method | Description |
|--------|-------------|
| `upload_linkedin_profile(profile_data)` | Upload profile |
| `optimize_linkedin_profile()` | AI optimization |
| `get_linkedin_profile()` | Get stored profile |

---

### Free Tools

All tools accept a resume file path, process it, and return a report.

| Method | Description |
|--------|-------------|
| `run_ats_check(file_path)` | ATS compatibility check |
| `run_resume_roast(file_path)` | Brutally honest roast |
| `run_career_clock(file_path)` | Career clock analysis |
| `run_interview_questions_tool(file_path)` | Interview questions |
| `run_career_personality_test(file_path)` | Personality test |
| `run_skills_gap_analyzer(file_path)` | Skills gap analysis |
| `run_readability_test(file_path)` | Readability test |
| `run_linkedin_profile_generator(file_path)` | LinkedIn generator |
| `run_buzzword_detector(file_path)` | Buzzword detection |
| `run_job_search_keywords(file_path)` | Search keywords |
| `run_networking_copilot(file_path)` | Networking strategy |

Each tool also has a `get_*_report(analysis_id)` method to retrieve a previous report.

---

### Personalized Learning

| Method | Description |
|--------|-------------|
| `create_course(**kwargs)` | Create a course |
| `list_courses()` | List courses |
| `get_course(course_id)` | Get course |
| `revise_slide(course_id, **kwargs)` | Revise a slide |
| `create_mindmap(course_id)` | Create mindmap |
| `clarify_course(course_id, message)` | Ask question |

### Website Resume (Portfolio)

| Method | Description |
|--------|-------------|
| `list_website_styles()` | Available styles |
| `create_website_resume(**kwargs)` | Generate portfolio |
| `list_website_resumes()` | List portfolios |
| `get_website_resume(id)` | Get portfolio |
| `regenerate_website_resume(id, style_id)` | New style |
| `revise_website_resume(id, feedback)` | AI revise |
| `reset_website_resume(id)` | Reset to original |
| `update_website_resume_html(id, html)` | Save HTML |
| `delete_website_resume(id)` | Delete |

### Affiliate

| Method | Description |
|--------|-------------|
| `create_affiliate_code()` | Create code |
| `get_affiliate_stats()` | Get stats |
| `get_affiliate_transactions()` | Transaction history |
| `request_affiliate_payout()` | Request payout |

### Other

| Method | Description |
|--------|-------------|
| `customer_service_chat(message)` | Chat with support bot |
| `transcribe_audio(file_path)` | Transcribe audio |
| `report_bug(**kwargs)` | Report a bug |
| `get_latest_announcement()` | Latest announcement |
| `delete_account()` | Delete your account |

---

## Error Handling

```python
from resumly import ResumlyClient, RateLimitError, InsufficientCreditsError

client = ResumlyClient(api_key="rly_...")

try:
    resume = client.create_resume(job_url="https://example.com/job")
except RateLimitError as e:
    print(f"Slow down! Retry after {e.retry_after}s")
except InsufficientCreditsError:
    print("Out of credits — upgrade your plan")
```

The client auto-retries on 429 (rate limit) up to `max_retries` times.

| Exception | HTTP Code | When |
|-----------|-----------|------|
| `AuthenticationError` | 401 | Invalid or missing API key |
| `InsufficientCreditsError` | 403 | Not enough credits |
| `NotFoundError` | 404 | Resource not found |
| `ValidationError` | 422 | Invalid request |
| `RateLimitError` | 429 | Too many requests |
| `ResumlyError` | * | Any other error |

## Rate Limits

- **60 requests per minute**
- **1,000 requests per day**

## License

MIT
