Quickstart
Zero to your first 201 Created submission.
0. What you need
- A provisioned tenant: a tenant API id, an HMAC secret (inbound signing), and a webhook secret (inbound webhook verification). Sandbox keys are self-serve — open signup via the console. See Onboarding.
- A funded credit balance. Sandbox tenants start with an automatic signup grant; production is fiat top-up (see Credits).
- The base URL:
https://api.staging.gofundnode.com(sandbox) orhttps://api.gofundnode.com(production).
Two environments, one wire. The sandbox at
https://api.staging.gofundnode.com is live, with open signup and its own database; production runs at https://api.gofundnode.com. The wire is byte-identical — the only things that change between environments are the base URL and the secrets. Start in the sandbox, then promote by swapping those. See Onboarding §3.1. Install the SDK
npm i @gofundnode/vendor-client
# (shipped inside the gofundnode monorepo today; depend on it by path until published)
2. Submit (SDK)
The SDK owns HMAC signing, timestamping, retries, idempotency, and typed errors so you don't have to.
import { GoFundNodeClient } from "@gofundnode/vendor-client";
const client = new GoFundNodeClient({
baseUrl: process.env.GFN_API_BASE_URL!, // sandbox or production base URL
tenantApiId: process.env.GFN_TENANT_API_ID!, // sent as X-GFN-Tenant
hmacSecret: process.env.GFN_HMAC_SECRET!, // signs outbound requests
});
const sub = await client.submitAtsApplication({
idempotencyKey: `quickstart:${crypto.randomUUID()}`, // tenant-unique, stable per logical job
tier: "standard", // optional (default "standard")
payload: {
applicationUrl: "https://job-boards.greenhouse.io/example/jobs/12345",
atsType: "greenhouse",
applicantProfile: { fullName: "Ada Lovelace", email: "ada@example.com" },
},
});
console.log(sub.submissionId, sub.status);
// { submissionId, status, quote: { estimated, max, surge }, balance: { after, held } }
// quote.max is the credit hold; the unused remainder is refunded at settlement.
2b. Submit (raw HMAC, no SDK)
If you can't use the SDK, sign <timestamp>.<raw_body> with HMAC-SHA256. The body must be signed byte-for-byte as transmitted.
TS=$(date +%s)
BODY='{"jobType":"ats.application","tier":"standard","idempotencyKey":"quickstart-001","payload":{"applicationUrl":"https://job-boards.greenhouse.io/example/jobs/12345","atsType":"greenhouse","applicantProfile":{}}}'
SIG=$(printf '%s' "${TS}.${BODY}" | openssl dgst -sha256 -hmac "$GFN_HMAC_SECRET" -r | cut -d' ' -f1)
curl -sS -X POST "$GFN_API_BASE_URL/v1/submissions" \
-H "X-GFN-Tenant: $GFN_TENANT_API_ID" \
-H "X-GFN-Timestamp: $TS" \
-H "X-GFN-Signature: $SIG" \
-H "Content-Type: application/json" \
--data-binary "$BODY"
Full signing rules: Auth & HMAC signing.
3. Watch it settle
Two ways to learn the outcome. Prefer webhooks; poll only as a fallback.
- Webhook (recommended): you receive
submission.completed/submission.failed/submission.cancelledat your configured URL. See Receive & verify webhooks. - Poll:
GET /v1/submissions/:iduntilstatusis terminal (submitted/failed/cancelled).
import { TERMINAL_STATUSES } from "@gofundnode/vendor-client";
let detail = await client.getSubmission(sub.submissionId);
while (!TERMINAL_STATUSES.has(detail.status)) {
await new Promise((r) => setTimeout(r, 3_000));
detail = await client.getSubmission(sub.submissionId);
}
console.log(detail.status, detail.actualCredits);
4. Run the bundled examples
# submit + poll one application
GFN_API_BASE_URL=… GFN_TENANT_API_ID=… GFN_HMAC_SECRET=… \
npx tsx examples/submit.ts
# idempotent re-submit + 409 conflict handling
npx tsx examples/idempotency-retry.ts
# read-only account snapshot (rate card + balance [+ submission])
GFN_SUBMISSION_ID=… npx tsx examples/account-snapshot.ts
# webhook receiver (separate terminal)
GFN_WEBHOOK_SECRET=… npx tsx examples/webhook-handler.ts