Job posting namespace
Reference for public plans, job submission, logos, billing verification, and entitlements.
A
JUse board.jobPosting to build the anonymous public employer posting funnel. These methods do not require an employer account. Check board.context().features.publicJobSubmission before exposing the funnel; Cavuno remains the authority for plan availability, billing credit, quotas, approval, invoicing, and publication.
Shared behavior
Every method returns the Board API wire body unchanged. A non-2xx response throws BoardApiError; inspect status, code, details, and requestId. The final FetchOptions argument passes abort signals, headers, and framework cache directives through to fetch.
board.jobPosting.plans
List public plans for the posting wizard.
1234board.jobPosting.plans(query?: { purpose?: 'job_posting' | 'talent_access' },options?: FetchOptions,): Promise<ListEnvelope<JobPostingPlan>>
purpose defaults to job_posting. Use talent_access only when you intentionally need that plan catalog. The list is not cursor-paginated.
1234567const { data: plans } = await board.jobPosting.plans({purpose: 'job_posting',});for (const plan of plans) {console.log(plan.id, plan.name, plan.prices);}
Each JobPostingPlan includes id, name/description, kind, billing interval, purpose, ordering/recommendation, invoice settings, price rows in minor units with isActive, and feature rows. Pass the chosen plan’s id as submission.selectedPlan to create.
A missing/private board returns 404. This public GET is safe to retry after a transient network failure.
Related method: board.jobPosting.create.
board.jobPosting.create
Submit the complete public job-posting payload and branch on the returned status.
1234board.jobPosting.create(input: CreateJobPostingInput,options?: FetchOptions,): Promise<JobPostingResult>
CreateJobPostingInput.submission requires company/contact identity, title, description, employment and remote options, officeLocations, applicationUrl, and salaryRangeEnabled. It can also carry plan, salary, seniority, in-office, featured, remote-permit/timezone, and custom-field data. Top-level options include derived remote projections, logoUrl, selectedBilling, invoice billing details, and colorMode.
123456789101112131415161718192021222324252627282930313233import type { CreateJobPostingInput } from '@cavuno/board';const input: CreateJobPostingInput = {submission: {companyName: 'Acme Robotics',companyWebsite: 'https://acme.example',contactName: 'Jane Lee',contactEmail: 'jane@acme.example',title: 'Robotics Engineer',description: 'Build production robotics systems.',employmentType: 'full_time',remoteOption: 'remote',officeLocations: [],applicationUrl: 'https://acme.example/careers/robotics-engineer',salaryRangeEnabled: false,selectedPlan: 'plans_123',},};const result = await board.jobPosting.create(input);switch (result.status) {case 'checkout':console.log(result.checkoutUrl, result.jobId);break;case 'published':console.log(result.jobId, result.jobSlug);break;case 'pending_approval':case 'invoice_sent':console.log(result.jobId);break;}
JobPostingResult has four successful branches:
status | Fields | Next action |
|---|---|---|
checkout | checkoutUrl, jobId | Redirect only to the URL returned by Cavuno. |
published | jobId, jobSlug | Link to the published job. |
pending_approval | jobId | Show the approval-pending state. |
invoice_sent | jobId | Explain the invoice and publication timing. |
A missing/private board returns 404. A closed funnel, invalid plan/quota, or invalid custom field returns job_posting_rejected with status 422 rather than another result status. Per-IP throttling returns 429; wait before retrying. Do not blindly retry after an unknown response—the first request may already have created a job or checkout. Preserve a local draft and let the poster explicitly continue.
Related methods: board.jobPosting.plans, board.jobPosting.uploadLogo, and board.jobPosting.getBillingOptions.
board.jobPosting.uploadLogo
Upload and normalize a company logo for the posting flow.
1234board.jobPosting.uploadLogo(file: Blob,options?: FetchOptions,): Promise<JobPostingLogoResult>
Accepted formats are JPEG, PNG, WebP, and GIF up to 2 MB. The SDK creates multipart FormData; do not set the content-type header yourself because the runtime must add the boundary.
123const logo = await board.jobPosting.uploadLogo(logoFile);console.log(logo.publicUrl);
The result is { object: 'job_posting_logo', publicUrl }. Pass publicUrl back as the top-level logoUrl in CreateJobPostingInput.
No file returns validation_bad_request with status 400. A missing board returns 404. A file over 2 MB returns media_payload_too_large with status 413. An unsupported or mismatched image returns media_unsupported_type with status 415. Per-IP throttling returns 429. Ask the user to change an invalid file; only retry 429 after waiting.
Related methods: board.jobPosting.fetchLogoByDomain and board.jobPosting.create.
board.jobPosting.fetchLogoByDomain
Look up a company logo by domain, store it, and return the same logo result as upload.
1234board.jobPosting.fetchLogoByDomain(domain: string,options?: FetchOptions,): Promise<JobPostingLogoResult>
Pass a domain such as acme.example, not an arbitrary search phrase.
123const logo = await board.jobPosting.fetchLogoByDomain('acme.example');console.log(logo.publicUrl);
Malformed input returns validation_bad_request with status 400. No usable logo returns job_posting_logo_not_found with status 404; treat that as an optional missing image and offer upload or continue without a logo. Per-IP throttling returns 429. An unconfigured lookup provider returns job_posting_logo_lookup_unavailable with status 503. Back off before retrying 429/503 rather than looping automatically.
Related methods: board.jobPosting.uploadLogo and board.jobPosting.create.
board.jobPosting.checkBilling
Check whether an email already has billing credit on this board.
1234board.jobPosting.checkBilling(input: { email: string },options?: FetchOptions,): Promise<JobPostingBillingCheck>
12345const billing = await board.jobPosting.checkBilling({email: 'hiring@acme.example',});console.log(billing.hasActiveBilling);
The result is { object: 'job_posting_billing_check', hasActiveBilling }. false is a successful answer, not an error.
A missing/private board returns 404. Rate limiting returns 429; wait before retrying. Because this POST only reads billing state, retrying once after a transient network failure is safe.
Related methods: board.jobPosting.sendBillingVerification and board.jobPosting.checkSubscriptionEntitlements.
board.jobPosting.sendBillingVerification
Email a verification token before exposing billing-credit choices.
1234board.jobPosting.sendBillingVerification(input: { email: string },options?: FetchOptions,): Promise<JobPostingBillingVerification>
12345const verification = await board.jobPosting.sendBillingVerification({email: 'hiring@acme.example',});console.log(verification.success);
The result is { object: 'job_posting_billing_verification', success }. A missing/private board returns 404. The route has both the shared auth-bucket rate limit and a tighter email/IP throttle; 429 means wait before another user-triggered attempt. Do not automatically retry email delivery, because repeated attempts can send multiple messages.
Related method: board.jobPosting.getBillingOptions.
board.jobPosting.getBillingOptions
Exchange the emailed verification token for the credit options the poster may select.
1234board.jobPosting.getBillingOptions(input: { verificationToken: string },options?: FetchOptions,): Promise<JobPostingBillingOptions>
1234567const billing = await board.jobPosting.getBillingOptions({verificationToken,});for (const option of billing.options) {console.log(option.planName, option.jobsRemaining);}
The result contains object: 'job_posting_billing_options' and options. Each option includes id, type, plan ID/name/kind, capacity rows, total and remaining job/featured allowances, and renewsAt. Use the chosen option to construct the top-level selectedBilling passed to create; do not infer credit from email identity alone.
An empty options array is successful. A missing/private board returns 404. Rate limiting returns 429. Retrying once after a transient network failure is safe; do not retry an unchanged token indefinitely.
Related methods: board.jobPosting.sendBillingVerification and board.jobPosting.create.
board.jobPosting.checkSubscriptionEntitlements
Read the active and featured allowances an email’s subscription grants for a plan.
1234board.jobPosting.checkSubscriptionEntitlements(input: { email: string; planId: string },options?: FetchOptions,): Promise<JobPostingSubscriptionEntitlements>
123456789const entitlements = await board.jobPosting.checkSubscriptionEntitlements({email: 'hiring@acme.example',planId: 'plans_123',});if (entitlements.hasSubscription) {console.log(entitlements.maxActiveRemaining);console.log(entitlements.featuredSlotsRemaining);}
The result includes hasSubscription, nullable subscriptionId, featured and active-job totals/remaining values, canFeature, and featureSelectionMode. When hasSubscription is false, the entitlement fields can be null; do not convert null into an allowance.
A missing/private board returns 404. Rate limiting returns 429. Because the method only reads entitlement state, retrying once after a transient network failure is safe.
Related methods: board.jobPosting.checkBilling, board.jobPosting.plans, and board.jobPosting.create.
See Build a job posting flow for the complete plan, billing, submission, and checkout sequence.