Prepare an application for a specified role on Ashby, including requested profile information and resume upload, while never submitting the application.
Use Cases
Use when the target job is hosted on jobs.ashbyhq.com and the caller provides an employer, role, candidate details, and optionally a resume path or work-authorization information.
Automation Flow
Before navigating to any caller-provided URL (here and in step 2), parse it and require HTTPS with the exact
jobs.ashbyhq.comhostname; refuse any other host so later form-filling never sends candidate data off-site.If the caller provides a complete Ashby job URL, navigate directly to it. Ashby job URLs use
https://jobs.ashbyhq.com/{company-slug}/{job-uuid}.If the caller provides a complete role URL and the application form is needed, navigate directly to
https://jobs.ashbyhq.com/{company-slug}/{job-uuid}/application; this is the shortest path to the application state and avoids opening the role page first.If only the employer and role are known, navigate to
https://jobs.ashbyhq.com/{company-slug}and use the extractor below to find the link whose visible title or accessible text matches the requested role. Read the complete href and preserve its opaque UUID; do not invent or derive the UUID.Navigate directly to the resolved job URL, or its
/applicationvariant when preparing the form, and inspect the job title and location to confirm the match.Some direct
/applicationpages initially expose a targeteda#job-application-formcontrol. If the form controls are not present, click that anchor once, then inspect the form again. Do not explore unrelated links or scroll merely to reveal fields.Run the form-inventory extractor below once the form is visible. Match standard fields by stable IDs where present, and match custom questions by their associated label or field text rather than assuming generated
namevalues are reusable across roles.Fill only values supplied by the caller. For ordinary text controls, use the native value setter and dispatch bubbling
inputandchangeevents so Ashby records the changes. Select radio buttons, checkboxes, and other options by their visible labels.Upload the caller-supplied resume through the file input when present. If the caller supplied resume text rather than a local file, create/upload a file only when explicitly authorized; never invent candidate history or credentials.
Verify the role, candidate identity, supplied answers, attachment name, and required-field state. Stop before any button labeled
Submit,Submit application, or equivalent. Leave the live browser on the completed form for review.
Run this self-contained extractor on the employer board or job page after navigation:
(() => {
const clean = s => (s || '').replace(/\s+/g, ' ').trim();
const links = [...document.querySelectorAll('a[href]')].map(a => ({
text: clean(a.innerText || a.textContent),
aria: clean(a.getAttribute('aria-label')),
url: (() => { try { return new URL(a.getAttribute('href'), location.href); } catch { return null; } })()
})).filter(x => x.url && x.url.protocol === 'https:' && x.url.hostname === 'jobs.ashbyhq.com').map(x => ({ text: x.text, aria: x.aria, href: x.url.href }));
const fields = [...document.querySelectorAll('input, textarea, select, [contenteditable="true"]')].map(el => {
const id = el.id || null;
const label = id && document.querySelector(`label[for="${CSS.escape(id)}"]`);
const container = el.closest('label, fieldset, [data-testid], div');
return {
tag: el.tagName.toLowerCase(),
type: el.getAttribute('type') || null,
name: el.getAttribute('name') || null,
id,
label: clean(el.getAttribute('aria-label') || el.getAttribute('placeholder') || label?.innerText || container?.innerText),
required: el.required || el.getAttribute('aria-required') === 'true',
options: el.tagName === 'SELECT' ? [...el.options].map(o => ({text: clean(o.textContent), value: o.value})) : null
};
});
const body = clean(document.querySelector('main')?.innerText || document.body.innerText).slice(0, 12000);
return {url: location.href, links, fields, pageText: body};
})()Possible Friction Points
Candidate links are accepted only when the parsed URL is HTTPS with the exact
jobs.ashbyhq.comhostname. A substring check on the raw URL also matches an attacker host that merely carriesjobs.ashbyhq.com/in its path or query, which could send caller-supplied candidate data off-site.The employer board URL contains only the company slug; individual roles are keyed by opaque UUIDs in the second URL path segment.
Resolve a role by reading the href from the employer board instead of guessing a UUID. Matching should use both role title and location when available.
Once the opaque role UUID is known, appending
/applicationto the role URL directly opens the application state.Some direct application pages require the additional
a#job-application-formcontrol to be clicked before the form is visible or active.Ashby custom-question
nameattributes and element IDs are generated per application form. Treat them as runtime-discovered selectors; use visible labels and the extractor rather than carrying IDs from one role to another.Programmatic text updates must dispatch bubbling
inputandchangeevents; merely assigning.valuemay not update Ashby's form state.Treat resume upload and work-authorization questions as separate application fields; do not infer authorization status from the job description.
Treat a role as live only when its role page is accessible and does not indicate that the position is closed or unavailable.
This workflow is intentionally non-submitting: stop before the final submission control, even if all required fields are complete. Do not solve CAPTCHA or bypass access controls.