openapi: 3.2.0 info: contact: email: support@aiapplyd.com name: AI Applyd url: https://aiapplyd.com description: 'The AI Applyd API runs the whole job hunt for you: it matches you to roles that fit your profile, rewrites your resume for each posting, writes the cover letter the form asks for, and auto-applies across the major applicant tracking systems.' license: name: Proprietary url: https://aiapplyd.com/legal/terms title: AI Applyd Outreach API version: 115c47ce servers: - description: Current environment url: https://api.aiapplyd.com/api/v1 security: - BearerAuth: [] tags: - name: outreach paths: /outreach/job/{jobId}/contacts: get: tags: - outreach summary: Get Outreach Contacts For A Saved Job description: List sourced hiring contacts for a SAVED job's company, before any application exists. Outreach is generated server-side at save time, so this is normally a cache read. Same payload as the application-scoped endpoint, including the addresses the employer printed in the job description itself. parameters: - schema: type: integer description: job_listing row id example: 4321 exclusiveMinimum: 0 required: true description: job_listing row id name: jobId in: path responses: '200': description: Contacts retrieved (cached or freshly sourced) content: application/json: schema: $ref: '#/components/schemas/OutreachContactsResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' operationId: getOutreachJobByJobIdContacts /outreach/job/{jobId}/draft: post: tags: - outreach summary: Draft Outreach Message For A Saved Job description: Generate a personalized outreach DM/email draft for one sourced contact on a SAVED job, before any application exists. Drafts only; the user sends from their own account. parameters: - schema: type: integer description: job_listing row id example: 4321 exclusiveMinimum: 0 required: true description: job_listing row id name: jobId in: path requestBody: content: application/json: schema: $ref: '#/components/schemas/OutreachDraftBody' responses: '200': description: Draft generated content: application/json: schema: $ref: '#/components/schemas/OutreachDraftResponse' '400': description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' operationId: postOutreachJobByJobIdDraft /outreach/{applicationResultId}/contacts: get: tags: - outreach summary: Get Outreach Contacts description: List sourced hiring contacts for the application's company. Returns cached rows when present; otherwise triggers a cheap sourcing run (team page -> Google SERP -> derived email), rate-limited to one run per company per 24h. Always returns a LinkedIn People-tab deep-link so the user is never at a dead end. parameters: - schema: type: integer description: Application result row id example: 1234 exclusiveMinimum: 0 required: true description: Application result row id name: applicationResultId in: path responses: '200': description: Contacts retrieved (cached or freshly sourced) content: application/json: schema: $ref: '#/components/schemas/OutreachContactsResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' operationId: getOutreachByApplicationResultIdContacts /outreach/{applicationResultId}/draft: post: tags: - outreach summary: Draft Outreach Message description: Generate a personalized outreach DM/email draft for one sourced contact. Reuses the follow-up draft generator (auto channel detection + anti-cliché filter). Returns the draft, channel, the contact's LinkedIn URL, and a pre-filled mailto link. Drafts only; the user sends from their own account. parameters: - schema: type: integer description: Application result row id example: 1234 exclusiveMinimum: 0 required: true description: Application result row id name: applicationResultId in: path requestBody: content: application/json: schema: $ref: '#/components/schemas/OutreachDraftBody' responses: '200': description: Draft generated content: application/json: schema: $ref: '#/components/schemas/OutreachDraftResponse' '400': description: Bad Request content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '401': description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/ApiErrorResponse' operationId: postOutreachByApplicationResultIdDraft components: schemas: OutreachContact: type: object properties: checkedAt: type: - string - 'null' description: ISO timestamp of when the public source for this person was last checked. Drives `stillThere`. example: '2026-09-05T10:00:00.000Z' confidence: type: - string - 'null' enum: - low - medium - high - null description: Confidence in this contact and its details example: low drafts: $ref: '#/components/schemas/OutreachContactDrafts' email: type: - string - 'null' description: 'Email address, or null when we hold none. An address may still ship with `emailStatus: unknown` - that means we hold it but cannot cite the page it came from.' example: jane.doe@acme.com emailEvidenceUrl: type: - string - 'null' description: 'verified: the public page the address appears on. pattern: the page the sample address was observed on. unknown: null.' example: https://acme.com/team emailPatternNote: type: - string - 'null' description: 'For `pattern` emails only: the pattern used and the real address it was observed on, so the user can judge the guess.' example: first.last, seen on john.smith@acme.com emailSource: type: - string - 'null' enum: - scraped - derived - verified - null description: 'Provenance of the email: scraped (read off a page) or derived (built from an observed pattern). ALWAYS null when `email` is null, so a provenance chip is never rendered beside an absent address.' example: derived emailStatus: type: string enum: - verified - pattern - unknown description: 'verified: appears on a public page we fetched AND `emailEvidenceUrl` names that page. pattern: inferred from a pattern observed on a real address at this domain. unknown: either no address at all, or an address we hold but cannot cite a page for - verified is never claimed without the page.' example: verified evidence: type: string description: 'One-sentence statement of what was actually matched for this person (name-to-profile, company-in-search-result, title-in-result). Never empty: a contact with no evidence is never surfaced.' example: Their name matches this LinkedIn profile, and the public search result lists them as Technical Recruiter at Acme. fullName: type: string description: Full name of the contact example: Jane Doe id: type: integer description: company_contact row id example: 9001 exclusiveMinimum: 0 evidenceUrl: type: - string - 'null' description: The public page the evidence sentence was derived from (the company team page, or the search-results page). Null for rows sourced before this field existed. example: https://acme.com/team linkedinUrl: type: - string - 'null' description: Public LinkedIn profile URL (deep-link only) example: https://www.linkedin.com/in/jane-doe rank: type: integer description: Position in the relevance ranking for the applied role (1 = best). Recruiters, hiring managers and team leads rank first. example: 1 exclusiveMinimum: 0 seniority: type: - string - 'null' enum: - recruiter - hiring_manager - lead - exec - null description: Coarse seniority bucket example: recruiter sourcedAt: type: - string - 'null' description: ISO timestamp of when this contact was last sourced/refreshed from public pages. Outreach data ages - people change roles - so the UI surfaces this instead of asserting the row is current. example: '2026-07-30T10:00:00.000Z' source: type: - string - 'null' enum: - team_page - google_serp - null description: Where the contact was sourced (team_page | google_serp) example: team_page stillThere: type: string enum: - likely - possible - stale description: 'From the evidence date alone: likely (checked within 90 days), possible (91-180 days), stale (older than 180 days, verify before sending).' example: likely title: type: - string - 'null' description: Job title / role example: Senior Technical Recruiter required: - checkedAt - confidence - drafts - email - emailEvidenceUrl - emailPatternNote - emailSource - emailStatus - evidence - fullName - id - evidenceUrl - linkedinUrl - rank - seniority - sourcedAt - source - stillThere - title additionalProperties: false OutreachContactsResponse: type: object properties: data: $ref: '#/components/schemas/OutreachContactsData' meta: $ref: '#/components/schemas/ResponseMetadata' success: type: boolean enum: - true required: - data - success additionalProperties: false description: Successful API response OutreachDraftBody: type: object properties: contactId: type: integer description: company_contact row id to draft a message for example: 9001 exclusiveMinimum: 0 required: - contactId additionalProperties: false OutreachDraftResponse: type: object properties: data: $ref: '#/components/schemas/OutreachDraftData' meta: $ref: '#/components/schemas/ResponseMetadata' success: type: boolean enum: - true required: - data - success additionalProperties: false description: Successful API response ResponseMetadata: type: object properties: duration: type: number description: Request duration in milliseconds example: 150 exclusiveMinimum: 0 performance: type: object properties: dbQueries: type: integer minimum: 0 totalDbTime: type: number minimum: 0 description: Performance metrics (preview/local only) requestId: type: string description: Unique request identifier example: req_abc123 timestamp: type: string format: date-time description: ISO 8601 timestamp example: '2026-09-25T17:29:47.224Z' version: type: string description: API version example: v1 OutreachDraftData: type: object properties: alternates: type: array items: $ref: '#/components/schemas/OutreachDraftAlternate' description: The SAME message written for every other channel, generated in the same request and grounded in the same evidence. A cold outreach is not one route - the user can paste into LinkedIn or into an email client without regenerating. body: type: string description: Drafted outreach message. For LinkedIn channel, capped at 300 chars. example: Hi Jane, I just applied for the Senior Backend Engineer role... channel: type: string enum: - email - linkedin - other description: Auto-detected channel (email when an address is known, else linkedin) example: email linkedinUrl: type: - string - 'null' description: LinkedIn URL of the contact, for the "Open on LinkedIn" deep-link example: https://www.linkedin.com/in/jane-doe mailto: type: - string - 'null' description: 'Pre-filled mailto: link when the contact has an email, else null' example: mailto:jane.doe@acme.com?subject=...&body=... subject: type: string description: Email subject line. Empty when channel=linkedin. example: Following up on my application required: - alternates - body - channel - linkedinUrl - mailto - subject additionalProperties: false OutreachLinkedinNote: type: object properties: body: type: string maxLength: 300 description: LinkedIn connection note, 300 characters or fewer (LinkedIn's limit). required: - body additionalProperties: false OutreachContactDrafts: type: object properties: email: $ref: '#/components/schemas/OutreachEmailDraft' linkedin: $ref: '#/components/schemas/OutreachLinkedinNote' required: - email - linkedin additionalProperties: false description: 'Deterministic drafts for this person: an email (subject + body) and a LinkedIn connection note. Nothing here is sent by us.' OutreachCompany: type: - object - 'null' properties: checkedAt: type: - string - 'null' description: ISO timestamp of when the company's own site was last fetched for this block. Null when nothing was fetched yet. example: '2026-09-05T10:00:00.000Z' domain: type: - string - 'null' description: Company web domain on file (no scheme, no www). example: acme.com evidenceUrl: type: - string - 'null' description: The page `hq`, `socials` and the site-published industry tags were read from. Null when nothing was fetched (the industry on our own record may still appear in `industryTags`). example: https://acme.com/ hq: type: - string - 'null' description: Headquarters as the company states it in its own structured data (schema.org Organization address). Null when not published. example: Berlin, Germany industryTags: type: array items: type: string description: 'Up to 6 industry / category tags, deduped. Two origins, and only two: what the company states about itself in its own structured data (schema.org `industry` / `knowsAbout`, read from `evidenceUrl`), and the industry on OUR record for this company, which is not page evidence and may be absent from the site. SEO `` is deliberately excluded - it is a crawler keyword dump, not a category list. Empty when neither origin has anything.' example: - Software - Fintech name: type: string description: Company name as it appears on the job example: Acme socials: type: array items: $ref: '#/components/schemas/OutreachSocialHandle' description: Social handles linked from the company's own homepage / footer, one per platform, each with the page it was found on. website: type: - string - 'null' description: Homepage URL. Null when the company has no domain on file. example: https://acme.com/ required: - checkedAt - domain - evidenceUrl - hq - industryTags - name - socials - website additionalProperties: false description: Company research block from the company's own site. Null when the job carries no company name. OutreachSocialHandle: type: object properties: evidenceUrl: type: string description: The company page the handle was linked from (homepage or footer). example: https://acme.com/ platform: type: string enum: - linkedin - x - instagram - youtube - discord - github description: Social platform of a handle found on the company's own website example: linkedin url: type: string description: The handle URL exactly as linked on the company's own site. example: https://www.linkedin.com/company/acme required: - evidenceUrl - platform - url additionalProperties: false OutreachDraftAlternate: type: object properties: body: type: string description: Draft body for this channel. LinkedIn variants are capped at 300 chars. channel: type: string enum: - email - linkedin - other description: Channel this variant is written for example: email mailto: type: - string - 'null' description: 'Pre-filled mailto: link when this variant is the email channel and an address is known.' subject: type: string description: Subject line. Empty for the LinkedIn variant. required: - body - channel - mailto - subject additionalProperties: false OutreachContactsData: type: object properties: company: $ref: '#/components/schemas/OutreachCompany' contacts: type: array items: $ref: '#/components/schemas/OutreachContact' description: The top 3 sourced contacts, ranked by relevance to the applied role (recruiter / hiring manager / team lead first). People whose evidence says they left are never included. postingEmails: type: array items: type: string description: Contact addresses the employer printed in this job description. Extracted verbatim from the stored posting - no model, no inference, no network call - so they are the highest-provenance contact on this surface. Empty for most postings. example: - careers@acme.com peopleDeepLink: type: string description: LinkedIn company People-tab deep-link so the user can keep searching themselves example: https://www.linkedin.com/company/acme/people/?keywords=recruiter rateLimited: type: boolean description: True when a fresh sourcing run was skipped (one run per company per 24h) example: false sourced: type: boolean description: True when this request triggered a fresh sourcing run example: true totalContacts: type: integer minimum: 0 description: How many verified contacts exist for this company before the top-3 cut. example: 7 required: - company - contacts - postingEmails - peopleDeepLink - rateLimited - sourced - totalContacts additionalProperties: false OutreachEmailDraft: type: object properties: body: type: string description: Ready-to-paste email body, first person, no clichés (anti-cliché filter applied). mailto: type: - string - 'null' description: 'Pre-filled mailto: link when the contact has an address, else null.' subject: type: string description: Email subject line. example: Applying for Senior Backend Engineer at Acme required: - body - mailto - subject additionalProperties: false ApiErrorResponse: type: object properties: error: type: object properties: code: type: string context: oneOf: - type: object properties: errorType: type: string enum: - validation field: type: string fieldErrors: type: array items: type: object properties: code: type: string expected: type: string field: type: string message: type: string received: type: string required: - field - message schemaName: type: string required: - errorType - type: object properties: attemptedEmail: type: string format: email errorType: type: string enum: - authentication failureReason: type: string enum: - invalid_credentials - account_locked - expired_token - token_expired - missing_token - invalid_token - session_expired description: Reason for authentication failure example: token_expired ipAddress: type: string operation: type: string service: type: string userAgent: type: string required: - errorType - type: object properties: actualPermission: type: string errorType: type: string enum: - authorization requiredPermission: type: string resource: type: string resourceId: type: string userId: type: string required: - errorType - type: object properties: constraint: type: string errorType: type: string enum: - database operation: type: string enum: - insert - update - delete - select - batch - transaction - migration description: Type of database operation example: select resourceId: type: string sqlState: type: string table: type: string userId: type: string required: - errorType - type: object properties: endpoint: type: string errorType: type: string enum: - external_service httpStatus: type: number operation: type: string resourceId: type: string responseTime: type: number retryAttempt: type: integer service: type: string serviceName: type: string userId: type: string required: - errorType - type: object properties: errorType: type: string enum: - resource resource: type: string resourceId: type: string service: type: string userId: type: string required: - errorType - type: object properties: errorType: type: string enum: - rate_limit limit: type: number resetTime: type: string windowMs: type: number required: - errorType - type: object properties: configKey: type: string errorType: type: string enum: - configuration operation: type: string service: type: string required: - errorType description: Type-safe error context information example: errorType: validation fieldErrors: - field: email message: Invalid email format details: oneOf: - type: object properties: detailType: type: string enum: - validation validationErrors: type: array items: type: object properties: code: type: string field: type: string message: type: string required: - field - message required: - detailType - validationErrors additionalProperties: false - type: object properties: detailType: type: string enum: - service_error originalError: type: string serviceName: type: string required: - detailType - serviceName additionalProperties: false - type: object properties: currentSize: type: number detailType: type: string enum: - batch originalError: type: string statementCount: type: number required: - detailType additionalProperties: false - type: object properties: detailType: type: string enum: - rate_limit limit: type: number resetTime: type: string windowMs: type: number required: - detailType - limit - resetTime - windowMs additionalProperties: false - type: object properties: conflictingField: type: string detailType: type: string enum: - resource resourceId: anyOf: - type: string - type: number resourceType: type: string required: - detailType additionalProperties: false - type: object properties: detailType: type: string enum: - generic info: type: string required: - detailType additionalProperties: false - type: object properties: currentTokens: type: integer minimum: 0 detailType: type: string enum: - insufficient_tokens requiredTokens: type: integer minimum: 0 required: - currentTokens - detailType - requiredTokens additionalProperties: false message: type: string validation: type: array items: type: object properties: code: type: string field: type: string message: type: string required: - field - message additionalProperties: false required: - code - message additionalProperties: false meta: $ref: '#/components/schemas/ResponseMetadata' success: type: boolean enum: - false required: - error - success additionalProperties: false securitySchemes: BearerAuth: description: 'Session token from signing in to AI Applyd, sent as `Authorization: Bearer `.' scheme: bearer type: http