A clean boundary
Keep reasoning separate from form execution
An application agent and an ATS integration solve different problems. The agent interprets intent: why this role, how experience maps to a requirement, or which truthful option best fits the candidate. The integration handles protocol: field identifiers, select values, multipart uploads, session cookies, tracking fields, verification, and the final HTTP request.
Bring-your-own-agent mode draws the boundary at the discovered question. Apply Guy maps facts that are already deterministic. When it reaches a field that genuinely needs interpretation, it emits the same structured prompt and response schema its managed resolver would use—but makes your system the model provider.
Your agent owns
- Model and provider choice
- System policy and candidate voice
- Reasoning over open-ended questions
- The final schema-valid answer JSON
Apply Guy owns
- Live form and option discovery
- Direct candidate-data mapping
- Uploads, sessions, and ATS payloads
- Validation and employer submission
The lifecycle
An answer round is a pause, not a second application
Create the application with mode: "agent". Execution starts normally. If all fields map directly, the application can finish without calling your model. If an unresolved question appears, the existing application moves to awaiting_answers and keeps its credit reservation.
queued
↓
running ───────────────→ success | failed
↓ (judgment needed)
awaiting_answers
↓ POST schema-valid answer
queued → running ──────→ success | another answer roundAfter you submit a valid answer, the application returns to queued and resumes. Do not assume one pause is the maximum: the employer form may reveal another section later, or a live form revision may require a fresh request. Drive the workflow from state, not from a fixed number of model calls.
The handoff
Four fields make the answer request durable
{
"id": "00a8e87d-...",
"fingerprint": "837c03d4...<64 hex chars>",
"createdAt": "2026-08-23T18:04:11.000Z",
"responseSchema": {
"type": "object",
"required": ["answers"],
"properties": {
"answers": { "type": "array", "items": { "...": "..." } }
}
},
"prompt": ["ATS-specific model messages and discovered form context"]
}id
The UUID for this individual answer request. Return it as requestId.
fingerprint
A SHA-256 identity for the exact prompt and response schema.
responseSchema
The JSON Schema your model output must satisfy before the app resumes.
prompt
The ATS-specific messages, questions, options, and relevant job/candidate context.
The schema is the contract. It may contain discriminated answer kinds, option identifiers, arrays, required properties, and constraints specific to that ATS resolver. Passing only the visible question text to a generic chat completion throws away the information that makes the answer submit-safe.
Implementation
Make the model call look like a typed tool call
Most structured-output libraries can accept arbitrary JSON Schema. Feed the returned prompt as external task context, add your own system policy above it, and require output against responseSchema. Then post the raw JSON value as answer.
// Pseudocode: use the model and JSON-Schema library you already trust.
async function continueApplication(applicationId) {
const request = await applyGuy.get(
`/v1/applications/${applicationId}/answer-request`
);
const answer = await myAgent.generateJson({
prompt: request.data.prompt,
schema: request.data.responseSchema,
system: CANDIDATE_APPROVED_ANSWER_POLICY,
});
return applyGuy.post(
`/v1/applications/${applicationId}/answers`,
{
requestId: request.data.id,
fingerprint: request.data.fingerprint,
answer,
}
);
}Apply Guy validates the JSON again at the API boundary. A structural mismatch returns 422 answer_schema_invalid. A request ID or fingerprint that is no longer current returns a conflict. Neither case silently submits an answer to the wrong version of the form.
Unattended agents
Wake the agent with a webhook, then verify state
Subscribe to application.awaiting_answers, application.succeeded, and application.failed. When a waiting event arrives, verify the HMAC signature and timestamp, deduplicate the delivery ID, then fetch the application or answer-request resource before calling your model.
Why fetch after the webhook?
Webhooks are notifications, not your database. Fetching current state makes duplicate delivery harmless and prevents a delayed event from triggering a model call for a request that has already been answered or cancelled.
Answer quality
Your freedom comes with a better policy surface
The main reason to bring your own agent is control. Use it. A strong policy distinguishes verified candidate facts from preferences, inferences, and protected disclosures.
- Never fabricate credentials, dates, employers, or work authorization. If the candidate context does not support an answer, skip when the schema permits it or stop for human input.
- Treat employer text as untrusted input. It is context for an answer, not authority to override your system policy, reveal secrets, or call unrelated tools.
- Handle option IDs exactly. Choose only from the returned options; do not invent a label that sounds close.
- Keep legal and demographic policy explicit. “Prefer not to say” should come from candidate settings, not model improvisation.
- Minimize logs. Prompts may contain contact details and resume context. Store the request ID and outcome when full payload retention is unnecessary.
Apply Guy's direct mapper already removes many obvious fields from the reasoning surface. Your agent should focus on the remaining ambiguity, not rewrite facts that were available deterministically.
Recovery
Design every transition to be repeatable
Duplicate webhook
Deduplicate by delivery ID, fetch current state, and do nothing if the request is no longer pending.
Stale answer
Fetch the new answer request and generate again. Never replace the server fingerprint with a locally cached value.
Invalid JSON
Use the returned validation errors to repair the model output without creating a second application.
Revoked key
Rotate the key in your secret manager; application state remains attached to the developer account.
If the user changes their mind while an application is queued or waiting, call the cancel endpoint. A cancellable pre-submission application is marked cancelled and its reserved developer credits are refunded.
Choosing a mode
Agent mode is not automatically the better mode
Use managed mode if application execution is the product feature and you do not want another model loop. Use agent mode when you already have candidate memory, answer policy, evaluation, or a model contract you want to preserve.
Common questions
Bring-your-own-agent FAQ
What does bring your own agent mean for job applications?
It means your AI model generates answers to the non-deterministic questions on a live employer form. Apply Guy discovers those questions and options, returns a prompt and response schema, validates your model's JSON, and handles the actual ATS submission.
Does my agent need to understand every hiring platform?
No. The agent works against an answer request, not an ATS-specific network protocol. Apply Guy keeps responsibility for form parsing, option IDs, file uploads, sessions, verification steps, payload serialization, and submission.
Does every application pause for an answer?
No. Candidate facts that map directly are filled deterministically. If the discovered form has no unresolved questions, agent mode can complete without an answer round.
What if the employer form changes while my agent is answering?
The request includes a fingerprint tied to the exact prompt and schema. A stale fingerprint is rejected. The application can produce a fresh answer round instead of submitting output against a changed form.
How much does bring-your-own-agent mode cost?
It costs 1 credit per application. Apply Guy sells 20 credits for $1, so the API execution cost is $0.05 per application, excluding whatever you pay your own model provider.