Skip to content

Web calculations and API access are available. View Billing for payment and automatic top-up availability.

Developer reference

API documentation

Build a cost-per-mile calculation into your server application.

Create an account · Sign in · Verify email · API keys · Dashboard Usage

Current pricing · Current API catalog · OpenAPI · JSON Schema

Quickstart

Create an account, verify your email, create an ordinary API key, then run one example from your own server environment. This page only displays and copies text.

Illustrative example price: $0.005000 per new successful operation. Free allowance: $1.500000 per organization per UTC calendar month after verification, used first and never carried forward. Another key does not create another allowance. See /pricing and the API catalog for the current server price. This reference is not your balance or a promise that free funds remain.

Supply DOFT_API_KEY through your server secret environment. Never put it in browser code, a URL, cookies, logs or a shell-history literal. No test/live key modes exist.

Create a UUID v4 once for each new operation and export it as DOFT_IDEMPOTENCY_KEY. Save that value and the complete input for any deliberate manual retry within the replay window. Do not generate another UUID each time you rerun an example.

Examples require curl 7.76+ with --fail-with-body and Python 3.10+ for JSON validation, Node.js 24 native fetch, or Python 3.10+ standard library urllib. Each makes at most one request with a 20-second timeout, refuses redirects and handles HTTP errors separately from transport errors. They do not retry automatically.

API references: GET /api/v1/tools (catalogue, versions and priceUsd), GET /api/v1/tools/cost-per-mile/schema (schema), GET /api/v1/health (minimal health), GET /api/v1/usage (current-key aggregates, at most 31 UTC days). GET /api/v1/openapi.json provides the API specification.

Authentication

Send Authorization: Bearer followed by your ordinary API key. A key is dt_ followed by 43 base64url characters. The key is shown once at creation; store it as a server secret. API cookies are ignored and do not authenticate requests.

Every request checks the current key, expiry or revocation, organization status, tool scope and any allowed IPv4/IPv6 CIDR restriction. Without an expiry a key remains valid until revoked. Keys do not issue browser sessions. There is no test/live mode.

Rotate by creating a new key and then revoking the old one. Keys in the same organization share balance, allowance, spending limits and replay scope. A new tool never silently expands an existing scope. Revocation or account suspension also denies replay.

An owner can set 1–60 attempts per minute per key; the default is 60. The organization default is 120 attempts per minute and may be configured by the service. Attempts include validation failures and replay. Rate limits are separate from paid spending caps and successful operation charges; use the returned headers for the applicable bucket.

Default paid spending caps are $10.000000 per UTC day and $100.000000 per UTC month. Free usage and top-ups do not count as paid consumption. Manage purchases and optional automatic top-up in Billing when available; this page initiates no payment.

Cost per mile

POST JSON only: Content-Type: application/json; no compression; body at most 8192 bytes. Idempotency-Key is required: 16–128 ASCII letters, digits, underscore or hyphen; UUID v4 is recommended.

All costs are monthly integer cents, not dollars. Omitted costs default to 0. milesPerMonth is required. Reject unknown fields, duplicate JSON keys, null, strings and fractions.

The synthetic input below totals $10,050.00 for 9,500 miles. The same server formula and schema are used by the working API.

data contains integer cents and JSON-number USD per mile rounded to four decimal places. meta.operationCostUsd and meta.chargedUsd are exact USD strings with six decimal places; formula version 1.0.0 is separate from API v1.

Monthly input fields
FieldMeaning / groupUnitRequired / defaultInclusive range
truckPaymentCentsTruck payment
fixed
integer cents/monthOptional; 00–10000000
trailerPaymentCentsTrailer payment
fixed
integer cents/monthOptional; 00–10000000
insuranceCentsInsurance
fixed
integer cents/monthOptional; 00–10000000
permitsAndPlatesCentsPermits and plates
fixed
integer cents/monthOptional; 00–10000000
otherFixedCentsOther fixed costs
fixed
integer cents/monthOptional; 00–10000000
fuelCentsFuel
variable
integer cents/monthOptional; 00–10000000
maintenanceCentsMaintenance
variable
integer cents/monthOptional; 00–10000000
tiresCentsTires
variable
integer cents/monthOptional; 00–10000000
tollsCentsTolls
variable
integer cents/monthOptional; 00–10000000
driverPayCentsDriver pay
variable
integer cents/monthOptional; 00–10000000
otherVariableCentsOther variable costs
variable
integer cents/monthOptional; 00–10000000
milesPerMonthMiles driven per month
mileage
miles/monthRequired; no default1–50000

curl

#!/bin/sh
# curl 7.76+ and Python 3.10+. Set server environment securely first.
set -eu
if ! printf '%s' "${DOFT_API_KEY-}" | python3 -c 'import re,sys; sys.exit(0 if re.fullmatch(r"dt_[A-Za-z0-9_-]{43}",sys.stdin.read()) else 1)'; then
  echo 'Set a valid DOFT_API_KEY in the server environment.' >&2; exit 1
fi
if ! printf '%s' "${DOFT_IDEMPOTENCY_KEY-}" | python3 -c 'import re,sys; sys.exit(0 if re.fullmatch(r"[A-Za-z0-9_-]{16,128}",sys.stdin.read()) else 1)'; then
  echo 'Set the saved DOFT_IDEMPOTENCY_KEY for this operation.' >&2; exit 1
fi
response=$(mktemp)
trap 'rm -f "$response"' EXIT HUP INT TERM
if status=$(curl --disable --silent --show-error --fail-with-body --max-time 20 --max-redirs 0 \
  --request POST 'https://api.dofttools.com/api/v1/tools/cost-per-mile' \
  --header "Authorization: Bearer $DOFT_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $DOFT_IDEMPOTENCY_KEY" \
  --data-raw '{"truckPaymentCents":240000,"insuranceCents":115000,"fuelCents":570000,"maintenanceCents":80000,"milesPerMonth":9500}' \
  --output "$response" --write-out '%{http_code}'); then
  case "$status" in 2??) ;; *) echo "HTTP $status. No automatic retry." >&2; exit 1 ;; esac
else
  case "$status" in [345]??) echo "HTTP $status. No automatic retry." >&2 ;;
    *) echo 'Transport failure; outcome unknown. Preserve operation context.' >&2 ;; esac
  exit 1
fi
python3 - "$response" <<'PY'
import json, sys
try:
    with open(sys.argv[1], encoding='utf-8') as response:
        data = json.load(response)
except (OSError, ValueError):
    sys.exit('Non-JSON response; outcome unknown. Preserve operation context.')
print(json.dumps(data, indent=2))
PY

Node.js

// Node.js 24; save as request.mjs. Supply server environment securely.
const apiKey = process.env.DOFT_API_KEY ?? '';
const operationKey = process.env.DOFT_IDEMPOTENCY_KEY ?? '';
if (apiKey.length !== 46 || !/^dt_[A-Za-z0-9_-]{43}$/.test(apiKey) || /[^A-Za-z0-9_-]/.test(operationKey) || !/^[A-Za-z0-9_-]{16,128}$/.test(operationKey)) {
  console.error('Set valid DOFT_API_KEY and saved DOFT_IDEMPOTENCY_KEY.');
  process.exit(1);
}
try {
  const response = await fetch('https://api.dofttools.com/api/v1/tools/cost-per-mile', {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(20_000),
    headers: { Authorization: 'Bearer ' + apiKey, 'Content-Type': 'application/json', 'Idempotency-Key': operationKey },
    body: '{"truckPaymentCents":240000,"insuranceCents":115000,"fuelCents":570000,"maintenanceCents":80000,"milesPerMonth":9500}',
  });
  if (!response.ok) {
    console.error('HTTP ' + response.status + '. No automatic retry.');
    process.exitCode = 1;
  } else {
    try { console.log(JSON.stringify(await response.json(), null, 2)); }
    catch { console.error('Non-JSON response; outcome unknown. Preserve operation context.'); process.exitCode = 1; }
  }
} catch {
  console.error('Transport failure; outcome unknown. Preserve operation context.');
  process.exitCode = 1;
}

Python

# Python 3.10+; standard library only. Supply server environment securely.
import json, os, re, sys
import http.client
import urllib.request
import urllib.error
api_key = os.environ.get('DOFT_API_KEY', '')
operation_key = os.environ.get('DOFT_IDEMPOTENCY_KEY', '')
if not re.fullmatch(r'dt_[A-Za-z0-9_-]{43}', api_key) or not re.fullmatch(r'[A-Za-z0-9_-]{16,128}', operation_key):
    sys.exit('Set valid DOFT_API_KEY and saved DOFT_IDEMPOTENCY_KEY.')
class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None
request = urllib.request.Request(
    'https://api.dofttools.com/api/v1/tools/cost-per-mile', method='POST',
    headers={'Authorization': 'Bearer ' + api_key, 'Content-Type': 'application/json', 'Idempotency-Key': operation_key},
    data=b'{"truckPaymentCents":240000,"insuranceCents":115000,"fuelCents":570000,"maintenanceCents":80000,"milesPerMonth":9500}',
)
try:
    with urllib.request.build_opener(NoRedirect()).open(request, timeout=20) as response:
        if not 200 <= response.status < 300:
            sys.exit('HTTP ' + str(response.status) + '. No automatic retry.')
        try:
            data = json.load(response)
        except (ValueError, UnicodeError):
            sys.exit('Non-JSON response; outcome unknown. Preserve operation context.')
        print(json.dumps(data, indent=2))
except urllib.error.HTTPError as error:
    sys.exit('HTTP ' + str(error.code) + '. No automatic retry.')
except (urllib.error.URLError, OSError, http.client.HTTPException):
    sys.exit('Transport failure; outcome unknown. Preserve operation context.')

Synthetic success response

{
  "data": {
    "fixedCostCents": 355000,
    "variableCostCents": 650000,
    "totalCostCents": 1005000,
    "costPerMileUsd": 1.0579,
    "fixedPerMileUsd": 0.3737,
    "variablePerMileUsd": 0.6842,
    "breakdown": [
      {
        "field": "truckPaymentCents",
        "group": "fixed",
        "amountCents": 240000,
        "costPerMileUsd": 0.2526
      },
      {
        "field": "insuranceCents",
        "group": "fixed",
        "amountCents": 115000,
        "costPerMileUsd": 0.1211
      },
      {
        "field": "fuelCents",
        "group": "variable",
        "amountCents": 570000,
        "costPerMileUsd": 0.6
      },
      {
        "field": "maintenanceCents",
        "group": "variable",
        "amountCents": 80000,
        "costPerMileUsd": 0.0842
      }
    ]
  },
  "meta": {
    "tool": "cost-per-mile",
    "version": "1.0.0",
    "operationId": "73e0691d-eb7e-4563-9da0-7616f04a69da",
    "operationCostUsd": "0.005000",
    "chargedUsd": "0.005000",
    "replayed": false
  }
}

Read Usage

GET /api/v1/usage requires an ordinary authorized API key. It returns UTC daily aggregates for the current original key only, not other keys in the organization. Use dashboard Usage for organization-wide history.

Supply both from and to as YYYY-MM-DD, or omit both for the current UTC calendar month. from is inclusive and to exclusive; the interval must be positive and at most 31 UTC days. The example omits dates to read the current month.

count is the number of original successful API operations; costUsd is their original total cost, equal to freeUsedUsd + paidUsedUsd. Replays, failed attempts and WEB calculations add no usage rows.

correctedFreeUsd and correctedPaidUsd show corrections separately from original consumption. They are not new calculations and do not mean an automatic refund to a card. All USD amounts are exact strings with six decimal places. Days without operations are omitted.

The synthetic response below illustrates three operations at $0.005000 each, funded from free allowance. It is not your current price or balance. The current-key totals can differ from dashboard totals across all keys.

curl — Usage

#!/bin/sh
# curl 7.76+ and Python 3.10+. Set server environment securely first.
set -eu
if ! printf '%s' "${DOFT_API_KEY-}" | python3 -c 'import re,sys; sys.exit(0 if re.fullmatch(r"dt_[A-Za-z0-9_-]{43}",sys.stdin.read()) else 1)'; then
  echo 'Set a valid DOFT_API_KEY in the server environment.' >&2; exit 1
fi
response=$(mktemp)
trap 'rm -f "$response"' EXIT HUP INT TERM
if status=$(curl --disable --silent --show-error --fail-with-body --max-time 20 --max-redirs 0 \
  --request GET 'https://api.dofttools.com/api/v1/usage' \
  --header "Authorization: Bearer $DOFT_API_KEY" \
  --output "$response" --write-out '%{http_code}'); then
  case "$status" in 2??) ;; *) echo "HTTP $status. No automatic retry." >&2; exit 1 ;; esac
else
  case "$status" in [345]??) echo "HTTP $status. No automatic retry." >&2 ;;
    *) echo 'Transport failure; outcome unknown. Preserve operation context.' >&2 ;; esac
  exit 1
fi
python3 - "$response" <<'PY'
import json, sys
try:
    with open(sys.argv[1], encoding='utf-8') as response:
        data = json.load(response)
except (OSError, ValueError):
    sys.exit('Non-JSON response; outcome unknown. Preserve operation context.')
print(json.dumps(data, indent=2))
PY

Synthetic Usage response

{
  "from": "2026-10-01",
  "to": "2026-11-01",
  "daily": [
    {
      "day": "2026-10-23",
      "tool": "cost-per-mile",
      "count": 3,
      "costUsd": "0.015000",
      "freeUsedUsd": "0.015000",
      "paidUsedUsd": "0.000000",
      "correctedFreeUsd": "0.000000",
      "correctedPaidUsd": "0.000000"
    }
  ]
}

Errors and safe retries

Replay lasts 24 hours from operation_at of the successful transaction, within organization + tool + Idempotency-Key. Send the same complete input and a currently valid authorized key. A different input gives 409 idempotency_conflict; an operation still running gives 409 request_in_progress and Retry-After.

Replay preserves data, version, operationId and operationCostUsd; replayed becomes true and chargedUsd is "0.000000". X-Request-Id identifies each HTTP attempt, not the operation. Replay does not debit again, but attempts still count toward rate limits.

A timeout, broken connection, non-JSON or incomplete response before or after commit means the outcome is unknown. It does not prove that no charge occurred and does not promise an automatic refund. Keep the original key and input; inspect the outcome before a deliberate retry within 24 hours. Never use a blind automatic retry loop.

After 24 hours or loss of operation context, do not automatically retry: the same idempotency key may create a new paid operation. Revoked keys and blocked accounts cannot bypass restrictions through replay.

429 rate_limited is an attempt-frequency limit; 429 spend_limit_reached is a paid UTC spending cap. Both and 409 request_in_progress include Retry-After in integer seconds. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (Unix seconds) and X-RateLimit-Scope (key, organization or ip) describe the selected rate bucket when checked; early errors may omit them. Waiting does not guarantee success. Decide explicitly whether the original operation remains safe to retry.

X-Balance-Remaining-Usd is the available free + paid balance as a six-decimal USD string. An absent header means unknown or not applicable, never zero. Application API errors contain only {"error":{"code":"..."}}; no message or details fields are promised. Network, proxy and incomplete responses may have no JSON body or request ID.

The API is for server-to-server requests, with no third-party browser CORS. Omit Origin in ordinary server requests: if supplied it must equal the API origin, otherwise 403 origin_denied. Browser session, invalid_session, unsupported_auth_method for Bearer on WEB, CSRF, Turnstile challenge and web_quota_exceeded belong to WEB, not Bearer API prerequisites.

API errors reference
HTTPCodeSafe action
400invalid_request, idempotency_key_required, invalid_idempotency_keyCorrect the payload or required idempotency header before making a deliberate request.
401invalid_keyCheck the server secret, expiry and revocation; never expose the key while troubleshooting.
402insufficient_balanceReview the available free and paid balance; a new operation needs sufficient funds.
403scope_denied, ip_not_allowed, account_unavailable, origin_deniedReview tool scope, allowed server IP and organization restrictions. Do not bypass denial with replay.
404tool_not_found, not_foundCheck the host, active tool and documented route.
405method_not_allowedUse the supported method; inspect Allow. GET and HEAD never calculate.
408request_timeoutThe application stopped reading the request body after 5 seconds. Preserve operation context and assess a deliberate retry.
409idempotency_conflictDo not change input under an existing operation key. Check the original operation context.
409request_in_progressWait for Retry-After, then explicitly assess a same-input retry within the replay window.
413payload_too_largeKeep the JSON body within 8192 bytes.
415unsupported_media_typeUse application/json without Content-Encoding compression.
429rate_limitedCheck the indicated attempt-rate bucket and Retry-After. Waiting does not guarantee success; no automatic retry.
429spend_limit_reachedReview the paid UTC spending cap and Retry-After. This is separate from attempt-rate limits; no automatic retry.
503service_unavailablePreserve operation context and assess availability before a safe manual retry; never assume no charge.