Submit a submission

Enqueue a typed job for dispatch onto a residential node. At launch the only job type is ats.application.

POST /v1/submissions

Request body (the SDK builds this from submitAtsApplication):

{
  "jobType":        "ats.application",
  "tier":           "standard",
  "idempotencyKey": "af-2026-05-22-12345",
  "payload": {
    "applicationUrl":   "https://job-boards.greenhouse.io/example/jobs/4567890",
    "atsType":          "greenhouse",
    "applicantProfile": { "...": "opaque, passed through to the node" },
    "jobContext":       { "title": "Software Engineer", "company": "Example Co." }
  }
}
FieldReqNotes
idempotencyKeyyes1–128 chars, tenant-unique. See Idempotency.
jobTypeyesMust be in your allowed_job_types. Launch: ats.application.
tiernostandard | priority | instant. Defaults to standard. Affects rate card + SLA.
payloadyesJob-type-specific blob, validated against the jobType schema.

The response: quote + hold

{
  "submissionId": "9a1f…",
  "status":       "dispatched",
  "quote":   { "estimated": 109.6, "max": 254.2, "surge": 1.0 },
  "balance": { "after": 9593.4, "held": 254.2 }
}

quote.max is the worst-case charge and is the amount held against your balance at submit. quote.estimated is the expected charge. At settlement the actual charge is computed from metered resource usage and the unused remainder is refunded — see Credits and docs/CREDIT-SYSTEM.md.

Status codes

CodeMeaning
201Created.
200Idempotent replay (same key + same body); body carries idempotent: true.
401HMAC verification failed.
402insufficient_credits. Body: required.
403job_type_not_allowed — an authorization / product-contract denial (not a legal block; 451 is reserved, not used here).
409idempotency_key_conflict — same key, different body.
422validation_error / unknown_job_type / unknown_tier / quote_failed.
503dispatch_unavailable — no operator could accept; the hold is released.

Full taxonomy: Error taxonomy.

Poll a submission

GET /v1/submissions/:id (tenant-scoped). Prefer webhooks; poll only as a fallback.

import { TERMINAL_STATUSES } from "@gofundnode/vendor-client";

let detail = await client.getSubmission(submissionId);
while (!TERMINAL_STATUSES.has(detail.status)) {
  await new Promise((r) => setTimeout(r, 3_000));
  detail = await client.getSubmission(submissionId);
}
// detail.status ∈ submitted | failed | cancelled
// detail.actualCredits is set once settled.
Field-name note. GET /v1/submissions/:id returns the id as id on the raw wire, while POST and the webhook envelope call the same value submissionId. The SDK normalizes the GET to submissionId. Treat them as the same identifier (docs/V1-API.md §2.2).

Cancel a submission

POST /v1/submissions/:id/cancel. Idempotent. Cancel before dispatch refunds the full hold; cancel mid-run charges node_seconds × rate up to the cancel point (no base fee). After a terminal state you get 409 terminal_state.

const r = await client.cancelSubmission(submissionId);
// { submissionId, status: "cancelled", refund: { amount, charged } }

Cancellation is not a failure path: if the operator legitimately failed the job you'll see submission.failed, not submission.cancelled.

Reference: docs/V1-API.md §2.1–§2.3, and docs/CREDIT-SYSTEM.md §6.1 for cancellation settlement (both in the repository).
← Idempotency & retries Receive & verify webhooks →