manage-candidate-profile

ashbyhq.com/manage-candidate-profile-e0m4bl · updated May 21, 2026

MDX-style export adds YAML metadata + attribution linking explainx.ai and this canonical listing URL.

$browse install ashbyhq.com/manage-candidate-profile-e0m4bl
0 commentsdiscussion
summary

Search Ashby ATS for a candidate by email or name, read their full profile (contact info, applications, tags, custom fields, notes), add a note, and submit structured interview feedback against an application — via Ashby's documented REST API.

skill.md
name
manage-candidate-profile
title
'Ashby Candidate Profile — Search, Read & Update'
description
>- Search Ashby ATS for a candidate by email or name, read their full profile (contact info, applications, tags, custom fields, notes), add a note, and submit structured interview feedback against an application — via Ashby's documented REST API.
website
ashbyhq.com
category
ats
tags
- ats - recruiting - ashby - candidate - api-first - notes - feedback
source
'browserbase: agent-runtime 2026-05-19'
updated
'2026-05-19'
recommended_method
api
alternative_methods
- method: browser rationale: >- app.ashbyhq.com has no email/password sign-in — only Google/Microsoft OAuth, SAML SSO, or magic-link. Browser automation requires a pre-warmed authenticated cookie jar and is brittle against UI changes. Use only as last resort when the API key is unobtainable.
verified
true
proxies
true

Ashby Candidate Profile — Search, Read & Update

Purpose

Operate on candidate records in an Ashby ATS tenant. Given a candidate identifier (email, name, or UUID), this skill:

  • Searches for candidates by email and/or name.
  • Reads the full candidate profile — contact info, social links, tags, current position/company/school, application ids, file handles (resume + attachments), custom fields, source, location, fraud status, and the list of existing notes.
  • Writes new information — adds a note to the candidate (plain-text or HTML), and submits structured interview feedback against one of the candidate's applications.

The skill is API-first. Ashby publishes a complete, stable, documented REST API at https://api.ashbyhq.com, and writing to the candidate's notes / feedback streams via the API is the same operation a Recruiter performs in the UI — the entries appear in the activity timeline with the API-key's owning user as the author. The browser app at app.ashbyhq.com is reserved for read/write fallback only when the API key is unavailable, and even then it requires a logged-in SSO session (there is no email/password form to script against).

When to Use

  • Recruiting-ops bots that triage candidates by email/name and dump structured profile JSON for downstream LLM scoring.
  • "Look up this candidate before my call" assistants that paste a one-page summary (current company, applications, last note, latest stage) into Slack.
  • Auto-noting integrations: drop a note on the candidate after an external event (sourcing tool hand-off, interview scheduled in a third-party system, Zapier-style "candidate replied on LinkedIn").
  • Interviewer copilots that submit a structured Score + RichText feedback against a specific applicationId after a debrief.
  • Not for: hiring decisions, offer changes, stage transitions — those are separate Ashby endpoints (application.changeStage, offer.*) and should be packaged as their own skill.

Workflow

1. Authenticate

Ashby uses HTTP Basic Auth with the API key as the username and an empty password. Every request must also send Accept: application/json; version=1. There is no OAuth dance, no refresh, no per-user token — one API key per integration, scoped by the permissions checked when it was created.

ASHBY_API_KEY="<from tenant admin>"
AUTH=(-u "${ASHBY_API_KEY}:" -H "Accept: application/json; version=1" -H "Content-Type: application/json")
BASE="https://api.ashbyhq.com"

The required permission for each call is encoded in the endpoint name's module: read calls (candidate.search, candidate.info, candidate.list, candidate.listNotes) need candidatesRead; write calls (candidate.createNote, applicationFeedback.submit) need candidatesWrite. Verify with apiKey.info once at session start — that one round-trip prevents surprise 403s later in the workflow.

curl "${AUTH[@]}" -X POST "$BASE/apiKey.info" -d '{}'

2. Find the candidate (search → resolve UUID)

When the caller has only an email or display name, hit candidate.search. Email and name parameters combine with AND, so pass email alone for the most reliable hit; pass both only if you need to disambiguate name collisions.

curl "${AUTH[@]}" -X POST "$BASE/candidate.search" \
  -d '{"email": "[email protected]"}'

Response shape:

{
  "success": true,
  "results": [
    {
      "id": "e9ed20fd-d45f-4aad-8a00-a19bfba0083e",
      "name": "Ada Lovelace",
      "primaryEmailAddress": { "value": "[email protected]", "type": "Work", "isPrimary": true },
      "applicationIds": ["b7c8...", "a1b2..."],
      "profileUrl": "https://app.ashbyhq.com/candidates/e9ed20fd-...",
      "..."
    }
  ]
}

candidate.search is capped at 100 results and is not paginated — if you anticipate >100 matches (e.g., a common name across a large org) switch to candidate.list with cursor + syncToken pagination and post-filter locally.

If you already have the candidate UUID (e.g., from a webhook payload, an earlier search, or a profileUrl like https://app.ashbyhq.com/candidates/<uuid>), skip search and go straight to candidate.info.

3. Read full profile + existing notes

CID="e9ed20fd-d45f-4aad-8a00-a19bfba0083e"

# Full profile (includes applicationIds, fileHandles, customFields, tags, source, location)
curl "${AUTH[@]}" -X POST "$BASE/candidate.info" -d "{\"id\":\"$CID\"}"

# All notes (paginated; default + max limit = 100)
curl "${AUTH[@]}" -X POST "$BASE/candidate.listNotes" -d "{\"candidateId\":\"$CID\"}"

candidate.info.results.profileUrl is the deep-link a human recruiter would paste into Slack. applicationIds[] is the foreign key for any downstream write against a specific application (e.g., feedback). To resolve those into job-titled applications, fan out one application.info per id — they are returned as bare UUIDs.

To get a resume's actual download URL: take resumeFileHandle.handle (or any element from fileHandles[]) and call file.info with it; the response carries a short-lived signed URL.

4. Add a note

curl "${AUTH[@]}" -X POST "$BASE/candidate.createNote" \
  -d '{
    "candidateId": "'"$CID"'",
    "note": "Spoke 2026-05-19. Strong interest in IC role. Will resurface for Q3 pipeline.",
    "sendNotifications": false,
    "isPrivate": false
  }'

The note field can be a plain string (default text/plain) or an object { "type": "text/html", "value": "<b>bold</b> text" }. HTML is silently filtered to a small allow-list: <b> <i> <u> <a> <ul> <ol> <li> <code> <pre> — anything else is stripped server-side before the note is saved.

sendNotifications: true notifies users subscribed to the candidate; default is false. isPrivate: true requires the API key to additionally carry the "Allow access to non-offer private fields" permission, or the call will 403.

5. Submit structured feedback (against an application, not a candidate)

Feedback is keyed on the application (applicationId), not the candidate, and must reference a feedback form definition. Resolve the form once at integration setup:

# Discover the form definition you want (typically the default Interview form)
curl "${AUTH[@]}" -X POST "$BASE/feedbackFormDefinition.list" -d '{}'
# -> pick one and remember its id

Then submit:

APP_ID="b7c8d9e0-..."  # one element of candidate.info.results.applicationIds
FORM_ID="<from feedbackFormDefinition.list>"

curl "${AUTH[@]}" -X POST "$BASE/applicationFeedback.submit" \
  -d '{
    "feedbackForm": {
      "formDefinitionId": "'"$FORM_ID"'",
      "fieldSubmissions": [
        { "path": "_systemfield_overallRecommendation", "value": { "score": 3 } },
        { "path": "_systemfield_summary",               "value": { "type": "PlainText", "value": "Solid systems thinking. Recommend onsite." } }
      ]
    },
    "applicationId": "'"$APP_ID"'"
  }'

Each form field has a typed value contract — see the type matrix in Site-Specific Gotchas below; submitting the wrong shape returns success: false, errorInfo.code: "invalid_field_value". If userId is omitted, the feedback is credited to the API-key's owning user.

Browser fallback

Use the API. If you absolutely cannot get an API key, the only browser path is:

  1. Have a human authenticate app.ashbyhq.com interactively via Google SSO, Microsoft SSO, magic link, or SAML — there is no email/password form, so a headless agent cannot self-onboard.
  2. Once a logged-in session cookie is captured, navigate to https://app.ashbyhq.com/candidates/<candidateUuid>. The right-hand sidebar exposes "Notes" and "Feedback" panels.
  3. Notes: focus the rich-text composer, type, and click "Add Note". Feedback: open a specific application card, choose "Submit Feedback", fill the form, click "Submit".

This is fragile (DOM not stable across releases, no anti-bot tolerance built in, MFA on most tenants), and is not recommended. The API path is strictly better in every observable dimension: lower latency, structured input/output, idempotent, surfaces the same activity entries the UI would have written.

Site-Specific Gotchas

  • Errors come back as HTTP 200 + success: false. This is the single most important Ashby footgun. Standard 4xx codes are reserved for auth (401 missing key, 403 wrong/disabled key or missing permission). Everything else — bad UUID, malformed body, validation failure, business-rule rejection — returns 200 OK with { "success": false, "errorInfo": { "code": "...", "message": "...", "requestId": "..." } }. Never branch on response.ok or status code alone; always parse the body and branch on body.success. Log errorInfo.requestId for any failure — Ashby support requires it.
  • API key permissions are module-scoped, not endpoint-scoped. Read access to one candidate.* endpoint implies read access to all of them; same for write. But a key with candidatesRead cannot call candidate.createNote — it will 403 with missing_endpoint_permission. Call apiKey.info once at startup and verify the permission set matches the operations you plan to perform, rather than discovering 30 calls in.
  • Accept: application/json; version=1 is required, not optional. Omit it and the API responds with a generic 406-ish error. Version pinning prevents silent breaking changes when Ashby ships v2.
  • HTML notes are aggressively filtered. candidate.createNote with type: "text/html" accepts only <b> <i> <u> <a> <ul> <ol> <li> <code> <pre>. Tables, headers, images, divs, spans, classes, styles, scripts — all stripped silently before storage. Don't try to render a complex template; either flatten to plain text or stick to the supported tags.
  • isPrivate: true needs a separately-granted permission. Even with candidatesWrite, a private note requires "Allow access to non-offer private fields?" on the API key. Without it, the call fails — and because of the 200-with-success-false convention, you must check body.success to catch this. Default isPrivate: false is the safe path.
  • Feedback submits against applicationId, not candidateId. A candidate can have multiple applications (different jobs). applicationFeedback.submit requires the right applicationId from candidate.info.results.applicationIds[]. Pick the one tied to the job/interview the feedback is about — there is no "candidate-level" feedback channel.
  • Feedback field paths are not in the response shape — they live on the form definition. Each feedbackFormDefinition returns sections[].fields[].field.path (e.g., _systemfield_overallRecommendation, _systemfield_summary, or custom <uuid> paths). You must fetch the definition first and map your inputs onto those exact paths, with the correctly-shaped value object (see typed-value matrix below). Submitting an unknown path returns success: false with unknown_field in errorInfo.
  • Feedback field-value types are not interchangeable. The field's type on the form definition dictates the shape of the submitted value: Boolean/Number/String/LongText/Email/Phone/Url/Date (YYYY-MM-DD) are bare scalars; Score is {score: 1-4}; Currency is {value, currencyCode}; CompensationRange is {type:"compensation-range", minValue, maxValue, currencyCode, interval}; NumberRange is {type:"number-range", minValue, maxValue}; RichText is {type:"PlainText", value} (Ashby explicitly does not accept rich-text documents via the API, only plain text in this wrapper); MultiValueSelect is string[] of option values; ValueSelect is a single option value string; UUID is the raw uuid string or {value: "<uuid>"}. Mismatch → success: false, errorInfo.code ~= "invalid_field_value".
  • candidate.search is hard-capped at 100 results and not paginated. Use it for "I have an email or a near-exact name". Anything bulk — name-substring scans, daily-sync extraction, > 100 expected matches — must switch to candidate.list with the cursor/syncToken pagination contract.
  • candidate.list pagination is opaque-cursor + sync-token. First call sends {} or {createdAfter: <ms-epoch>}; response carries nextCursor and moreDataAvailable. Pass nextCursor on the next call. When moreDataAvailable: false, persist the returned syncToken and pass it on your next polling cycle to fetch only deltas. Limit caps at 100 per page (default 100). Don't try to grep around the cursor; treat it as a black box.
  • candidate.search ignores extra parameters silently. Sending company, phone, tagId, etc. doesn't error — they're just dropped. The only filters are email and name.
  • Resume / file URLs are not in candidate.info. You get fileHandles[].handle and resumeFileHandle.handle — call file.info with the handle to mint a short-lived signed URL. Don't try to construct a download URL from the handle directly.
  • profileUrl returned in candidate responses is the only canonical deep-link to the Ashby UI. Format: https://app.ashbyhq.com/candidates/<uuid>. Use it for human-facing summaries; never try to scrape it.
  • Rate limits live in X-RateLimit-* response headers; standard limit is 100 req/min per key. X-Ratelimit-Limit, X-Ratelimit-Remaining, X-Ratelimit-Reset (unix epoch) are returned on every response. On burst workloads, throttle to remaining/window or you'll get 429s. The 429 still comes back as a real HTTP status — that one is not wrapped in success: false.
  • Webhooks beat polling for change events. If you're building "react when a candidate progresses", subscribe via webhook.create to candidateStageChange, candidateHire, candidateMerge, applicationUpdate, etc., instead of polling candidate.list with syncToken. Same data, far lower cost.
  • The app.ashbyhq.com SPA has no public email/password form. Sign-in routes only to Google OAuth, Microsoft OAuth, SAML SSO, or a magic link emailed to the user. Browser-driving without a pre-warmed cookie jar is not possible. Captured 2026-05-19 at https://app.ashbyhq.com/signin — no <input type="password"> anywhere on the page.
  • Don't curl developers.ashbyhq.com/openapi.json expecting an OpenAPI spec — there isn't a public one. The reference site is a ReadMe.com-rendered manual. Trust the documented endpoint shapes; trust the live errorInfo codes for everything else.

Expected Output

The skill produces one envelope per invocation. Three shapes by outcome:

1. Successful search + read (no writes)

{
  "success": true,
  "operation": "read",
  "candidate": {
    "id": "e9ed20fd-d45f-4aad-8a00-a19bfba0083e",
    "name": "Ada Lovelace",
    "primaryEmail": "[email protected]",
    "primaryPhone": "+1-555-0142",
    "position": "Senior Staff Engineer",
    "company": "Babbage Engines Inc.",
    "school": "University of London",
    "tags": [{ "id": "...", "title": "Top Candidate" }],
    "socialLinks": [{ "type": "LinkedIn", "url": "https://linkedin.com/in/ada" }],
    "applicationIds": ["b7c8d9e0-...", "a1b2c3d4-..."],
    "resumeHandle": "fh_aB1cD2eF3...",
    "customFields": [{ "id": "...", "title": "Years of Experience", "value": 12 }],
    "source": { "title": "Referral - Engineering" },
    "location": { "locationSummary": "London, UK" },
    "fraudStatus": "PassedFraudCheck",
    "profileUrl": "https://app.ashbyhq.com/candidates/e9ed20fd-d45f-4aad-8a00-a19bfba0083e",
    "notes": [
      {
        "id": "n_001",
        "createdAt": "2026-05-12T14:33:21.000Z",
        "isPrivate": false,
        "content": "Initial outreach — replied within 4h.",
        "author": { "id": "u_001", "firstName": "Joey", "lastName": "Joe", "email": "[email protected]" }
      }
    ]
  }
}

2. Successful write (note added)

{
  "success": true,
  "operation": "createNote",
  "candidateId": "e9ed20fd-d45f-4aad-8a00-a19bfba0083e",
  "note": {
    "id": "n_002",
    "createdAt": "2026-05-19T17:04:55.123Z",
    "isPrivate": false,
    "content": "Spoke 2026-05-19. Strong interest in IC role. Will resurface for Q3 pipeline.",
    "author": { "id": "u_001", "firstName": "Joey", "lastName": "Joe", "email": "[email protected]" }
  }
}

3. Successful write (feedback submitted)

{
  "success": true,
  "operation": "submitFeedback",
  "applicationId": "b7c8d9e0-...",
  "submittedFormInstanceId": "sfi_001",
  "submittedValues": {
    "_systemfield_overallRecommendation": { "score": 3 },
    "_systemfield_summary": { "type": "PlainText", "value": "Solid systems thinking. Recommend onsite." }
  }
}

4. Failure (no candidate match)

{
  "success": false,
  "reason": "candidate_not_found",
  "query": { "email": "[email protected]" }
}

5. Failure (Ashby API rejected — note the success: false body even when HTTP is 200)

{
  "success": false,
  "reason": "api_error",
  "httpStatus": 200,
  "errorInfo": {
    "code": "missing_endpoint_permission",
    "message": "The supplied API key does not have permission to access this endpoint",
    "requestId": "01JSJ8FEK5ZN4XQBZP7DBKK7ZC"
  }
}
how to use manage-candidate-profile

How to use manage-candidate-profile on Cursor

AI-first code editor with Composer

1

Prerequisites

Before installing skills in Cursor, ensure your development environment meets these requirements:

  • Cursor installed and configured on your development machine
  • Node.js version 16.0+ with npm package manager (verify with node --version)
  • Active project directory or workspace where you want to add manage-candidate-profile
2

Execute installation command

Execute the skills CLI command in your project's root directory to begin installation:

$browse install ashbyhq.com/manage-candidate-profile-e0m4bl

The skills CLI fetches manage-candidate-profile from GitHub repository ashbyhq.com/manage-candidate-profile-e0m4bl and configures it for Cursor.

3

Select Cursor when prompted

The CLI will show a list of available agents. Use arrow keys to navigate and space to select Cursor:

◆ Which agents do you want to install to?
│ ── Universal (.agents/skills) ── always included ────
│ • Amp
│ • Antigravity
│ • Cline
│ • Codex
│ ●Cursor(selected)
│ • Cursor
│ • Windsurf
4

Verify installation

Confirm successful installation by checking the skill directory location:

.cursor/skills/manage-candidate-profile

Reload or restart Cursor to activate manage-candidate-profile. Access the skill through slash commands (e.g., /manage-candidate-profile) or your agent's skill management interface.

Security & Verification Notice

We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.

Skills execute code in your development environment. Always verify the publisher's identity, review recent commits, and test in isolated environments before production deployment.

List & Monetize Your Skill

Submit your Claude Code skill and start earning

GET_STARTED →

Use Cases

Task Automation & Efficiency

Automate repetitive workflows and reduce manual effort

Example

Generate reports, summarize documents, draft communications

Save 3-5 hours per week on routine tasks

Knowledge Enhancement

Learn new skills, understand complex topics, get expert guidance

Example

Explain concepts, provide examples, suggest learning resources

Accelerate learning and skill development by 2x

Quality Improvement

Enhance output quality through reviews, suggestions, and refinements

Example

Review drafts, suggest improvements, catch errors

Improve work quality by 30-40% with less effort

Implementation Guide

Prerequisites

  • Claude Desktop or compatible AI client with skill support
  • Clear understanding of task or problem to solve
  • Willingness to iterate and refine outputs

Time Estimate

15-45 minutes depending on use case complexity

Installation Steps

  1. 1.Install skill using provided installation command
  2. 2.Test with simple use case relevant to your work
  3. 3.Evaluate output quality and relevance
  4. 4.Iterate on prompts to improve results
  5. 5.Integrate into regular workflow if valuable

Common Pitfalls

  • Expecting perfect results without iteration
  • Not providing enough context in prompts
  • Using skill for tasks outside its intended scope
  • Accepting outputs without review and validation

Best Practices

✓ Do

  • +Start with clear, specific prompts
  • +Provide relevant context and constraints
  • +Review and refine all outputs before using
  • +Iterate to improve output quality
  • +Document successful prompt patterns

✗ Don't

  • Don't use without understanding skill limitations
  • Don't skip validation of outputs
  • Don't share sensitive information in prompts
  • Don't expect skill to replace human judgment

💡 Pro Tips

  • Be specific about desired format and style
  • Ask for multiple options to choose from
  • Request explanations to understand reasoning
  • Combine AI efficiency with human expertise

When to Use This

✓ Use When

Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.

✗ Avoid When

Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.

Learning Path

  1. 1Familiarize yourself with skill capabilities and limitations
  2. 2Start with low-risk, non-critical tasks
  3. 3Progress to more complex and valuable use cases
  4. 4Build expertise through regular use and experimentation

Discussion

Product Hunt–style comments (not star reviews)
  • No comments yet — start the thread.
general reviews

Ratings

4.839 reviews
  • Soo Yang· Dec 28, 2024

    I recommend manage-candidate-profile for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.

  • Kofi Liu· Dec 20, 2024

    Registry listing for manage-candidate-profile matched our evaluation — installs cleanly and behaves as described in the markdown.

  • Shikha Mishra· Dec 12, 2024

    Solid pick for teams standardizing on skills: manage-candidate-profile is focused, and the summary matches what you get after install.

  • Ganesh Mohane· Dec 8, 2024

    Keeps context tight: manage-candidate-profile is the kind of skill you can hand to a new teammate without a long onboarding doc.

  • Meera Chawla· Dec 8, 2024

    Solid pick for teams standardizing on skills: manage-candidate-profile is focused, and the summary matches what you get after install.

  • Kofi Zhang· Nov 27, 2024

    We added manage-candidate-profile from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Meera Rahman· Nov 19, 2024

    manage-candidate-profile reduced setup friction for our internal harness; good balance of opinion and flexibility.

  • Arya Tandon· Nov 11, 2024

    Useful defaults in manage-candidate-profile — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.

  • Yash Thakker· Nov 3, 2024

    We added manage-candidate-profile from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.

  • Dhruvi Jain· Oct 22, 2024

    manage-candidate-profile fits our agent workflows well — practical, well scoped, and easy to wire into existing repos.

showing 1-10 of 39

1 / 4