# REST quickstart

Job Search is the available product. Use the current product metadata and quote; planned products are not callable. Prefer the [Console first-task guide](/docs/accounts/) for a guided flow, or choose an [agent/workflow kit](/ai-integrations/). The requests below use Bash, Node.js for key generation, and an API key stored in `TRUEFETCH_API_KEY`. Keep secrets and saved input out of shared shell history, logs and source control.

## 1. Create an API Key

Sign in, open **Developer → API Keys**, and create a key with the required scope and monthly budget. The secret appears once. Store it in your application's secret manager; it cannot be recovered from a command receipt.

## 2. Obtain a quote

Send an authenticated request to `POST /v1/quotes`:

```json
{
  "productId": "job-search",
  "input": {
    "keyword": "Software engineer", "country": "Germany",
    "maxResults": 3,
    "maxCredits": 3,
    "allowPartial": false,
    "platforms": ["arbeitnow"]
  }
}
```

Save the approved JSON above as `job-input.json` containing the **input object only**, then obtain the quote:

```bash
node --input-type=module -e 'import fs from "node:fs"; process.stdout.write(JSON.stringify({productId:"job-search",input:JSON.parse(fs.readFileSync("job-input.json","utf8"))}))' > quote-request.json
curl --fail-with-body --max-time 30 --max-redirs 0 \
  https://api.truefetch.io/v1/quotes \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @quote-request.json > quote.json
```

Review the returned normalized input, price version, integer credit units, limits and expiry. A quote does not execute a task, reserve credits or promise a record count.

`maxResults` is the ceiling for each selected platform. The quote's `resultLimit` is the total ceiling, equal to `maxResults` multiplied by the number of resolved platforms. The total cannot exceed 1,000; a larger request is rejected without silently reducing it. Review the resolved `input.platforms`, especially when you omit an explicit selection.

## 3. Submit exactly one command

Create a new canonical `Idempotency-Key` immediately before a new submission:

`v1.<Unix milliseconds>.<random base64url nonce>`

The nonce must contain 22–64 base64url characters. Keep the **entire original key** unchanged through recovery. The acceptance window is 24 hours; the server allows at most five minutes of future clock skew. Generate this file once for approved new work, and retain it with the exact input before submission:

```bash
node --input-type=module -e 'import {randomBytes} from "node:crypto"; import fs from "node:fs"; fs.writeFileSync("original-command-key.txt",`v1.${Date.now()}.${randomBytes(24).toString("base64url")}`,{flag:"wx"})'
```

`flag: "wx"` refuses to replace a saved key. A restart or uncertain response must use that existing file. Do not rerun key generation to discover whether work was accepted.

Send `POST /v1/tasks` with your Bearer API Key, the canonical `Idempotency-Key` header, and:

```json
{
  "productId": "job-search",
  "input": {
    "keyword": "Software engineer", "country": "Germany",
    "maxResults": 3,
    "maxCredits": 3,
    "allowPartial": false,
    "platforms": ["arbeitnow"]
  },
  "expectedPricingVersion": "<version returned by the quote>"
}
```

After reviewing `quote.json`, prepare the submission using its current price version:

```bash
node --input-type=module -e 'import fs from "node:fs"; const estimate=JSON.parse(fs.readFileSync("quote.json","utf8")).estimate; const input=JSON.parse(fs.readFileSync("job-input.json","utf8")); if(!estimate||estimate.productId!=="job-search"||typeof estimate.pricingVersion!=="string"||!estimate.pricingVersion||!Number.isFinite(Date.parse(estimate.expiresAt))||Date.parse(estimate.expiresAt)<=Date.now()) throw Error("Review a valid current quote first"); process.stdout.write(JSON.stringify({productId:"job-search",input,expectedPricingVersion:estimate.pricingVersion}))' > task-request.json
curl --fail-with-body --max-time 30 --max-redirs 0 \
  https://api.truefetch.io/v1/tasks \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY" \
  -H "Idempotency-Key: $(cat original-command-key.txt)" \
  -H 'Content-Type: application/json' \
  --data-binary @task-request.json > submission.json
```

This is a deliberate new submission. Do not wrap it in a retry loop. HTTP 200 can contain a processing, rejected or otherwise unconfirmed receipt; inspect `receipt.outcome` before using a Task reference. The server checks the quote version and budget. Framework kits additionally check quote-input/price consistency before posting.

An accepted command returns a receipt and task reference. Its frozen contract records price, budget and retention. Do not infer a successful submission from a network timeout, or create a fresh key to retry uncertain work.

## 4. Recover a lost response

Read `GET /v1/commands/tasks.create/{originalKey}` with the same authorized account. This read needs no input or fresh quote and never creates work:

```bash
curl --fail-with-body --max-time 30 --max-redirs 0 \
  "https://api.truefetch.io/v1/commands/tasks.create/$(cat original-command-key.txt)" \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY" > original-receipt.json
```

- `accepted`: persist the actual task reference in `receipt.result.objects`. A current Task projection can be null; its absence does not erase accepted evidence.
- `processing`: wait for the stated Retry-After, then explicitly check again.
- `rejected`: read the reason and correct the input before a new command.
- `unknown`: the original request may still be in flight. Check again or explicitly seal within its acceptance window.
- `not_accepted_final`: the server has sealed the original key; re-enter your inputs and create a new key for new work.
- `receipt_expired`: the server cannot establish whether the old command was accepted. Check Tasks or contact support.

See [command recovery](/docs/commands/). Terminal receipts are retained for at least seven days and may still be read after the new-command acceptance window ends. A task's current state does not prove the outcome of a particular create or cancel command.

## 5. Follow the existing task

Read `GET /v1/tasks/{taskId}`. Nonterminal tasks provide a retry interval. Failures do not authorize submitting duplicate work. Inspect source outcomes and the frozen billing facts at termination.

Set `TASK_ID` to the accepted receipt's Task reference, then read:

```bash
curl --fail-with-body --max-time 30 --max-redirs 0 \
  "https://api.truefetch.io/v1/tasks/$TASK_ID" \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY"
```

## 6. Read or download results

Use `GET /v1/tasks/{taskId}/results?pageSize=50`. Follow opaque continuation cursors without modifying them. The API returns bounded pages and does not collect or charge again when you read saved results.

```bash
curl --fail-with-body --max-time 30 --max-redirs 0 \
  "https://api.truefetch.io/v1/tasks/$TASK_ID/results?pageSize=50" \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY" > result-page.json
```

A single page is not a complete export. Continue only with the returned `meta.pagination.nextCursor`, URL-encoded as one opaque parameter, until `hasMore` is false and the received count agrees with the committed result count. A partial collection remains partial even after every retained record has been read.

Request fresh JSONL links through `POST /v1/tasks/{taskId}/download` with `{"format":"jsonl"}`. Download CSV through authenticated `GET /v1/tasks/{taskId}/download.csv`; the server streams the saved records. JSONL links are short-lived bearer credentials. Results expire seven days after terminal result commit; use the actual expiry on the task and download files you need before then. An interrupted stream is not a complete file.

## Request cancellation separately

Cancellation uses its own newly generated, persisted canonical key; use a separate file instead of replacing `original-command-key.txt`. It sends `POST /v1/tasks/{taskId}/cancel` with `{}`:

```bash
curl --fail-with-body --max-time 30 --max-redirs 0 \
  "https://api.truefetch.io/v1/tasks/$TASK_ID/cancel" \
  -H "Authorization: Bearer $TRUEFETCH_API_KEY" \
  -H "Idempotency-Key: $(cat cancellation-command-key.txt)" \
  -H 'Content-Type: application/json' --data '{}' > cancellation.json
```

If the response is lost, explicitly read `/v1/commands/tasks.cancel/{originalCancellationKey}`. Accepted cancellation does not promise a cancelled final Task; inspect current Task execution status separately. These requests never recover a one-time key secret or restore expired results.

[Result formats and retention](/docs/responses/) · [Source coverage and limits](/docs/job-search/) · [Billing](/docs/billing/) · [Errors and next steps](/docs/errors/) · [Contact support](/contact/)
