Skip to content

Resumly

The public pay-per-use client — the main entry point for all SDK operations. ResumlyClient is an alias of this class.

Resumly

Resumly(api_key: Optional[str] = None, base_url: str = _DEFAULT_BASE_URL, timeout: int = 180, max_retries: int = 3)

Client for the Resumly public API (/api/v1).

Parameters:

Name Type Description Default
api_key str

Your Resumly API key (starts with rly_). Falls back to the RESUMLY_API_KEY environment variable.

None
base_url str

API base URL. Defaults to https://api.resumly.ai.

_DEFAULT_BASE_URL
timeout int

HTTP request timeout in seconds. Defaults to 180 (AI operations can take a couple of minutes).

180
max_retries int

Auto-retry count on 429 (rate limit), honoring Retry-After. Defaults to 3.

3

Examples:

>>> from resumly import Resumly
>>> client = Resumly(api_key="rly_...")
>>> client.balance()
{'balance_usd': 5.0, ...}

balance

balance() -> dict

Current wallet balance. Free.

usage

usage(page: int = 1, page_size: int = 50) -> dict

Paginated ledger of metered charges, newest first. Free.

keys

keys() -> dict

List your API keys (masked). Free.

create_key

create_key(name: str) -> dict

Create a new API key. The full key is shown exactly once.

revoke_key

revoke_key(key_id: str) -> dict

Revoke an API key by id.

buy_credit

buy_credit(amount: Union[str, int]) -> str

Create a Stripe checkout for a wallet top-up; returns the checkout URL.

Parameters:

Name Type Description Default
amount str or int

One of "10", "25", "50", "100" (USD).

required

upload_base_resume

upload_base_resume(file_path: Union[str, Path], *, idempotency_key: Optional[str] = None) -> dict

Upload and parse a PDF/DOCX base resume. $0.05.

base_resume

base_resume() -> dict

Your parsed base resume JSON. Free.

update_base_resume

update_base_resume(resume_data: dict, expected_active_upload_id: Optional[str] = None) -> dict

Replace the parsed base resume data. Free.

Pass expected_active_upload_id for optimistic concurrency: the update is rejected if another upload became active in the meantime.

autofill_profile

autofill_profile() -> dict

Your auto-apply answer profile (work auth, EEO, phone, etc.). Free.

update_autofill_profile

update_autofill_profile(attributes: dict) -> dict

Update the auto-apply answer profile. Free.

instructions

instructions() -> dict

Your custom tailoring instructions. Free.

set_instructions

set_instructions(text: str) -> dict

Set custom tailoring instructions applied to every tailor. Free.

readiness

readiness() -> dict

Is this account ready to auto-apply (resume, profile, inbox)? Free.

queries

queries() -> dict

List saved searches with refresh metadata (last_refreshed_at, latest_job_posted, new_jobs_last_run, consecutive_empty_runs, supply_preview) so you can decide whether a refresh is worth $0.05. Free.

query

query(query_id: str) -> dict

Get one saved search. Free.

create_query

create_query(query_name: str, title_filter: List[str], **filters) -> dict

Create a saved search from structured filters. Free.

To build filters from plain English first, call :meth:interpret and pass the returned draft_patch fields here. Jobs are fetched only when you :meth:refresh the query.

update_query

update_query(query_id: str, **fields) -> dict

Partially update a saved search. Free.

delete_query

delete_query(query_id: str) -> dict

Delete a saved search. Free.

supply_check

supply_check(**filters) -> dict

Count how many jobs a draft search shape would match. Free, cached ~10 minutes. Same filter shape as :meth:create_query (name optional).

interpret

interpret(prompt: str, current_draft: Optional[dict] = None) -> dict

Turn plain English into validated search filters. Free (budget-capped).

Returns a draft_patch you can pass to :meth:create_query, plus assumptions made and asks that could not be mapped.

refresh

refresh(query_id: str, *, idempotency_key: Optional[str] = None) -> dict

Fetch fresh jobs for a saved search. $0.05, returns 202.

Poll :meth:query until last_refreshed_at advances, then read :meth:jobs. A 429 means a run is already in flight (not charged).

jobs

jobs(page: int = 1, page_size: int = 25, **filters) -> dict

Your matched-jobs board — every job your searches found, scored against your resume. Free.

Supported filters: query_id, auto_apply (true = only jobs the auto-apply fleet can submit), remote_only, last_n_days, date_from, date_to, sort_by ('match' or 'date'), is_saved, min_match_score / max_match_score (0..1), application_status, keyword, organization, has_salary_info, min_annual_salary, visa_sponsorship_only, hide_completed.

job_stats

job_stats(query_id: Optional[str] = None) -> dict

Board statistics: status lanes, sources, match-score bands. Free.

job

job(job_id: str) -> dict

Full job detail: description, url, score insight, organization. Free.

save_job

save_job(job_id: str) -> dict

Toggle save on a job. Free.

block_job

block_job(job_id: str) -> dict

Toggle block on a job. Free.

skip_job

skip_job(job_id: str) -> dict

Toggle skip on a job. Free.

import_job

import_job(url: str, raw_text: Optional[str] = None, *, idempotency_key: Optional[str] = None) -> dict

Import an outside posting into your feed, parsed and match-scored. $0.05.

Pass just url (Resumly fetches and parses it) or url plus raw_text (parsed directly; the url is still used for dedupe). Raises :class:DuplicateRequestError (409) if the URL is already in your feed — you are not charged.

tailor

tailor(job_id: Optional[str] = None, *, url: Optional[str] = None, description: Optional[str] = None, llm_model: Optional[str] = None, idempotency_key: Optional[str] = None) -> dict

Create a tailored resume. $0.25.

Three ways to point at a job:

  • tailor(job_id) — tailor for a job already on your board (POST /jobs/{id}/tailor).
  • tailor(url=...) — tailor for an outside posting URL.
  • tailor(description=...) — tailor for raw job-description text.

resumes

resumes(page: int = 1, page_size: int = 20) -> dict

List your tailored resumes, newest first. Free.

resume

resume(resume_id: str) -> dict

Get one tailored resume (full JSON). Free.

download

download(resume_id: str, path: Optional[Union[str, Path]] = None) -> bytes

Download the tailored resume as DOCX bytes. Free.

If path is given the bytes are also written to disk.

comparison

comparison(resume_id: str) -> dict

Base-vs-tailored comparison for a resume. Free.

metadata

metadata(resume_id: str) -> dict

Resume metadata (company, title, skills, match). Free.

delete_resume

delete_resume(resume_id: str) -> dict

Delete a tailored resume. Free.

cover_letter

cover_letter(resume_id: str, *, idempotency_key: Optional[str] = None) -> dict

Generate a cover letter for a tailored resume. $0.10.

get_cover_letter

get_cover_letter(resume_id: str) -> dict

Read the stored cover letter. Free.

export_pdf

export_pdf(resume_id: str, document_type: str = 'resume', *, idempotency_key: Optional[str] = None) -> dict

Start a PDF export of the resume or cover letter. $0.02, returns 202 with an operation_id — poll :meth:operation or use :meth:wait_operation.

Parameters:

Name Type Description Default
document_type str

"resume" (default) or "cover_letter".

'resume'

get_operation

get_operation(operation_id: str) -> dict

Status of any async operation. Free.

status is one of queued, running, succeeded, failed, or needs_attention (an application a human must finish — not a failure; the reservation stays open).

operations

operations(page: int = 1, page_size: int = 25, kind: Optional[str] = None) -> dict

Your recent async operations, newest first. Free.

wait

wait(operation_id: str, *, timeout: int = 600, poll_interval: float = 3.0, raise_on_failure: bool = True) -> dict

Block until an operation reaches a terminal state, then return it.

needs_attention is deliberately NOT terminal: an escalated application is still live. Set timeout to how long you are willing to wait for one; applications can legitimately take minutes.

Raises :class:ResumlyError on timeout, or on a failed operation unless raise_on_failure=False.

operation

operation(resume_id: str, operation_id: str) -> dict

Status of an async export operation, via the legacy per-resume route. Prefer :meth:get_operation, which works for every async operation. Free.

wait_operation

wait_operation(resume_id: str, operation_id: str, *, timeout: int = 180, poll_interval: float = 2.0) -> dict

Poll an export operation until it leaves the pending states.

Returns the final operation document (check its status and result URL). Raises :class:ResumlyError if timeout seconds pass first.

translate

translate(resume_id: str, target_language: str, document_type: Optional[str] = None, *, idempotency_key: Optional[str] = None) -> dict

Translate a tailored resume (or cover letter) to another language. $0.10.

rewrite

rewrite(instructions: str, *, idempotency_key: Optional[str] = None) -> dict

Rewrite your base resume following free-form instructions. $0.50.

interview_questions

interview_questions(job_id: str, *, idempotency_key: Optional[str] = None) -> dict

Generate interview questions for a job on your board. $0.10.

get_interview_questions

get_interview_questions(job_id: str) -> dict

Read stored interview questions. Free.

interview_answer

interview_answer(job_id: str, question_index: int, answer: str, *, idempotency_key: Optional[str] = None) -> dict

Get AI feedback on your answer to a generated question. $0.05.

company_research

company_research(job_id: str, *, idempotency_key: Optional[str] = None) -> dict

Run company research for a job's employer. $0.25.

get_company_research

get_company_research(job_id: str) -> dict

Read stored company research. Free.

eligible_jobs

eligible_jobs(page: int = 1, page_size: int = 25, *, query_id: Optional[str] = None, min_match_score: Optional[float] = None, keyword: Optional[str] = None, saved_only: bool = False) -> dict

Jobs the auto-apply fleet can submit for you. Free.

apply

apply(job_id: str, resume_id: str, *, idempotency_key: Optional[str] = None) -> dict

Queue an auto-apply submission. $0.50 is reserved and billed only on a confirmed submission; failures are refunded.

Returns 202 with a queue_job_id — track it via :meth:applications.

applications

applications(status: Optional[str] = None, job_ids: Optional[List[str]] = None, page: int = 1, page_size: int = 25) -> dict

List your auto-apply applications with status. Free.

application_stats

application_stats() -> dict

Application lane counts. Free.

cancel_application

cancel_application(queue_job_id: str) -> dict

Cancel a queued application (the reservation is released). Free.

retry_application

retry_application(queue_job_id: str, *, idempotency_key: Optional[str] = None) -> dict

Retry a failed application (a fresh $0.50 reservation applies).

autopilot

autopilot() -> dict

Autopilot configuration and state. Free.

enable_autopilot

enable_autopilot(**config) -> dict

Enable autopilot (e.g. job_queries=[...], min_match_score=70).

Autopilot runs consume the same wallet: each submission bills like :meth:apply, each search run like :meth:refresh.

update_autopilot

update_autopilot(**config) -> dict

Update autopilot settings (partial). Free.

pause_autopilot

pause_autopilot() -> dict

Pause autopilot. Free.

resume_autopilot

resume_autopilot() -> dict

Resume autopilot after a pause. Free.

disable_autopilot

disable_autopilot() -> dict

Disable autopilot. Free.

autopilot_dashboard

autopilot_dashboard() -> dict

Autopilot dashboard aggregates. Free.

autopilot_activity

autopilot_activity(page: int = 1, page_size: int = 25, status: Optional[str] = None) -> dict

Per-job autopilot activity, paginated. Free.

autopilot_runs

autopilot_runs(page: int = 1, page_size: int = 20) -> dict

Autopilot run history, paginated. Free.

inbox_status

inbox_status() -> dict

Inbox status and unread counts. Free.

inbox_emails

inbox_emails(page: int = 1, page_size: int = 25, *, category: Optional[str] = None, categories: Optional[List[str]] = None, is_read: Optional[bool] = None, search: Optional[str] = None, job_id: Optional[str] = None) -> dict

Classified employer emails (interview invites, rejections, offers, assessments...). Free.

categories (a list, sent CSV) overrides category.

inbox_email

inbox_email(email_id: str) -> dict

Read one email (marks it read). Free.

reply_email

reply_email(email_id: str, **body) -> dict

Reply to an employer email (e.g. body_text=... or body_html=...). Free.

compose_email

compose_email(**body) -> dict

Send a new email from your managed inbox (to=[...], subject=..., body_text=...). Free.

AgencyClient

B2B agency console — provision and manage client profiles with a full-scope agency key (from resumly.agency import AgencyClient).

AgencyClient

AgencyClient(api_key: Optional[str] = None, base_url: str = _DEFAULT_BASE_URL, timeout: int = 180, max_retries: int = 3)

Client for the Resumly agency (tenant-admin) API.

Parameters:

Name Type Description Default
api_key str

The agency admin's full-scope API key. Falls back to the RESUMLY_API_KEY environment variable.

None
base_url str

API base URL. Defaults to https://api.resumly.ai.

_DEFAULT_BASE_URL
timeout int

HTTP request timeout in seconds. Defaults to 180.

180
max_retries int

Auto-retry count on 429, honoring Retry-After. Defaults to 3.

3

create_client_profile

create_client_profile(name: str, email: str, *, external_id: Optional[str] = None, applications_per_day: Optional[int] = None, account_type: Optional[str] = None, location: Optional[str] = None) -> dict

Provision a client profile under your agency tenant.

Returns the profile plus api_key — shown exactly once. Store it; it cannot be retrieved later (use :meth:rotate_client_key if lost).

Parameters:

Name Type Description Default
name str

The client's full name.

required
email str

The client's apply email. Applications are submitted under this identity and employer replies are routed to it.

required
external_id str

Your own reference id for this client, echoed back on webhooks.

None
applications_per_day int

Per-client daily auto-apply cap. Defaults to the plan tier's cap.

None

list_client_profiles

list_client_profiles(*, page: int = 1, page_size: int = 50, include_deleted: bool = False) -> dict

List client profiles in your agency tenant.

update_client_profile

update_client_profile(profile_id: str, *, status: Optional[str] = None, applications_per_day: Optional[int] = None, name: Optional[str] = None, external_id: Optional[str] = None) -> dict

Update a client profile.

status accepts "active" or "suspended". Suspending pauses auto-apply, cancels queued jobs, and disables the client's API key until reactivated.

delete_client_profile

delete_client_profile(profile_id: str) -> dict

Delete a client profile. Keys are revoked; history is retained.

rotate_client_key

rotate_client_key(profile_id: str) -> dict

Revoke the client's existing API keys and issue a new one.

get_agency_usage

get_agency_usage(month: Optional[str] = None) -> dict

Billable successful applications per client for month (YYYY-MM).

get_webhook_config

get_webhook_config() -> dict

Current webhook configuration (the signing secret is never returned).

set_webhook_config

set_webhook_config(url: str, *, secret: Optional[str] = None, enabled: bool = True, events: Optional[List[str]] = None) -> dict

Set the endpoint that receives application events.

On first save the generated signing secret is returned once. Verify each delivery against the X-Resumly-Signature header, which is t=<timestamp>,v1=<hmac_sha256(secret, f"{t}.{body}")>.

send_test_webhook

send_test_webhook() -> dict

Send a webhook.test event and return the delivery outcome.

list_webhook_deliveries

list_webhook_deliveries(*, page: int = 1, page_size: int = 25, status: Optional[str] = None) -> dict

Recent webhook delivery attempts, newest first.