Prepare an Ashby job application without submitting

Site jobs.ashbyhq.comTask prepare-job-applicationVersion v4Updated Oct 2, 2026Category job applications

Locate an Ashby-hosted role by employer board, open its opaque-ID job URL, complete requested application fields and resume upload, and stop before final submission. This skill was captured from a live agent session on jobs.ashbyhq.com and is published here as a reusable recipe for agents.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

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

  1. Before navigating to any caller-provided URL (here and in step 2), parse it and require HTTPS with the exact jobs.ashbyhq.com hostname; refuse any other host so later form-filling never sends candidate data off-site.

  2. 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}.

  3. 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.

  4. 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.

  5. Navigate directly to the resolved job URL, or its /application variant when preparing the form, and inspect the job title and location to confirm the match.

  6. Some direct /application pages initially expose a targeted a#job-application-form control. 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.

  7. 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 name values are reusable across roles.

  8. Fill only values supplied by the caller. For ordinary text controls, use the native value setter and dispatch bubbling input and change events so Ashby records the changes. Select radio buttons, checkboxes, and other options by their visible labels.

  9. 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.

  10. 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.com hostname. A substring check on the raw URL also matches an attacker host that merely carries jobs.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 /application to the role URL directly opens the application state.

  • Some direct application pages require the additional a#job-application-form control to be clicked before the form is visible or active.

  • Ashby custom-question name attributes 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 input and change events; merely assigning .value may 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.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=jobs.ashbyhq.com&task=prepare-job-application