DECISIONS API
On this page

VANSA / API REFERENCE

Documentation

Vansa is a System One decision model for your software. Send context and questions to get a choice (choice), a score on your scale (score), or the probability of yes (noul), without generated text.

Base URL
https://api.vansa.org
Default model
vansa-3.5
Output
JSON · no generated text

Try the playground Install the agent skill Markdown reference OpenAPI schema

Quickstart

Set VANSA_KEY to an API key from the console, then run this request on your server. To try Vansa without a personal key, use the public playground.

curl https://api.vansa.org/v1/systemone \
  --max-time 100 \
  -H "Authorization: Bearer $VANSA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "vansa-3.5",
    "state": "Please refund my duplicate charge.",
    "questions": {
      "intent": {
        "type": "choice",
        "instructions": "What does the customer need?",
        "criteria": ["refund", "technical_support", "other"]
      }
    }
  }'

Read the selected option from answers.intent.choice and each option’s probability from answers.intent.probabilities. Question IDs are returned unchanged.

Multi-question request and responseRecorded example

This support-ticket request combines all three question types in one call.

curl https://api.vansa.org/v1/systemone \
  -H "Authorization: Bearer $VANSA_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "vansa-3.5",
    "state": {
      "subject": "Duplicate charge on invoice #4411",
      "body": "Hi, we were billed twice for March. Please refund the duplicate today or we will cancel our plan."
    },
    "questions": {
      "department": {"type": "choice", "instructions": "Which department should handle this request?",
                     "criteria": {"billing": "invoices, payments, refunds", "technical": "bugs, outages, system errors",
                                  "sales": "pricing, new contracts", "other": "everything else"}},
      "urgency":    {"type": "score", "instructions": "How urgent is this request?",
                     "criteria": ["not urgent", "soon", "critical deadline or blocking issue"]},
      "churn_risk": {"type": "noul", "instructions": "Does the customer threaten to cancel or leave?"},
      "refund_requested": {"type": "noul", "instructions": "Does the customer explicitly ask for a refund?"}
    }
  }'

Recorded response

{
  "id": "req_bfe3ef4ff5f38256",
  "object": "systemone.result",
  "model": "vansa-3",
  "created": 1790309369,
  "answers": {
    "department": {
      "type": "choice",
      "choice": "billing",
      "probabilities": {"billing": 0.9922, "technical": 0.0052, "sales": 0.0009, "other": 0.0017},
      "confidence": 0.9624
    },
    "urgency": {
      "type": "score",
      "score": 1.703,
      "legend": {"0": "not urgent", "1": "soon", "2": "critical deadline or blocking issue"},
      "probabilities": {"0": 0.0578, "1": 0.1815, "2": 0.7607},
      "confidence": 0.3788
    },
    "churn_risk":       {"type": "noul", "noul": 0.8568, "probabilities": {"false": 0.1432, "true": 0.8568}, "confidence": 0.8568},
    "refund_requested": {"type": "noul", "noul": 0.8794, "probabilities": {"false": 0.1206, "true": 0.8794}, "confidence": 0.8794}
  },
  "usage": {"input_tokens": 357, "output_tokens": 0, "state_tokens": 40, "truncated": false},
  "latency_ms": 711.5
}

This is recorded Vansa-3 output for the request above. Try the current model in the playground.

Authentication

POST /v1/systemone and GET /v1/me require a bearer key:

Authorization: Bearer vsk_your_key_here

To get a key, create an account in the console with your email address, Google or GitHub, then create API keys there. Keep them on your server; do not embed them in browser or mobile code. The playground uses a separate shared demo key for visitors.

Use GET /v1/me to check your key and remaining quota. GET /v1/models and GET /health are public.

You can also sign in to the usage console with an API key to see requests, input tokens, response times and estimated cost in USD and EUR. The console shows usage for that key only. See pricing and the usage calculator for the current rate. Usage estimates are not invoices.

Token balance

Request & response

POST /v1/systemone

Send JSON with Content-Type: application/json. Every question is evaluated against the same state. Request time grows with state length and question count.

Request body

FieldTypeDescription
modelstring · optionalDefaults to vansa-3.5. Aliases vansa, vansa-latest, vansa-1, vansa-2, vansa-3 and vansa-3.0 through vansa-3.4 all resolve to the current model. Model names are case-insensitive.
statestring · object · arrayRequired, non-empty input. Use named JSON fields to give context; arrays can represent conversation turns. The full prompt must fit within 16,384 tokens per question.
questionsobjectRequired map of 1–32 question IDs to definitions. IDs are non-empty strings of at most 64 characters and return unchanged in answers.
assetsobject · optionalUp to 4 PNG or JPEG images, keyed by a reference that you place in state. See Images.

Question definition

FieldTypeDescription
typestringRequired: choice, score or noul.
instructionsstringRequired, non-empty question. Maximum 4,000 characters. Name the state fields the question refers to.
criteriaobject · arraychoice: 2–255 named options as an object of descriptions, or an array of names.
score: 2–32 non-empty level descriptions, lowest first.
noul: optional non-empty object with true and/or false descriptions.

Response body

FieldDescription
idRequest ID; also returned in X-Request-Id. Include it when reporting a problem.
objectAlways systemone.result.
modelThe model that answered, including when an alias was requested.
createdUnix timestamp in seconds.
answersMap of question IDs to typed answers.
usage.input_tokensPrompt tokens read: the state (including its images) and questions, with shared content counted once.
usage.output_tokensAlways 0; the model does not generate text.
usage.state_tokensText tokens in the state alone; images are not included. Present when the API tokenizer is available.
usage.truncatedfalse for Vansa-3.5. Over-limit prompts receive 400 rather than being cut. Present when the API tokenizer is available; retained for compatibility.
usage.imagesRequests with images only: the number of images read.
usage.image_tokensRequests with images only: the tokens the images took, included in usage.input_tokens.
latency_msServer time in milliseconds, including time waiting for the model.

Images

A request can include up to 4 PNG or JPEG images in a top-level assets map, the same assets map as TypeSafe’s Jev (base64 images only). Each key is a reference that you also write, as text, in state: the image is placed at that point of the state and the reference stays there as its label, so a question can name it (“Does image 1 show …?”).

Request and response with an imagePython

A support ticket with a photo of the delivered box. parcel.jpg stands for any JPEG file; the data is fictional.

# pip install requests
import base64
import os
import requests

with open("parcel.jpg", "rb") as f:
    photo = base64.b64encode(f.read()).decode("ascii")  # plain base64, no data: prefix

response = requests.post(
    "https://api.vansa.org/v1/systemone",
    headers={"Authorization": f"Bearer {os.environ['VANSA_KEY']}"},
    json={
        "model": "vansa-3.5",
        "state": {
            "ticket": "The parcel arrived today. The customer asks for a replacement.",
            "photo": "image 1",
        },
        "assets": {"image 1": {"type": "image", "format": "jpg", "data_base64": photo}},
        "questions": {
            "box_damaged": {"type": "noul", "instructions": "Does image 1 show visible damage to the box?"},
            "next_step": {
                "type": "choice",
                "instructions": "What should support do with the request in `ticket`?",
                "criteria": {"send_replacement": "the box in image 1 is visibly damaged",
                             "ask_for_details": "image 1 does not show the problem clearly",
                             "other": None},
            },
        },
    },
    timeout=100,
)
response.raise_for_status()
print(response.json()["usage"])

Response shape

{
  "id": "req_…",
  "object": "systemone.result",
  "model": "vansa-3.5",
  "created": 1792569600,
  "answers": {
    "box_damaged": {"type": "noul", "noul": 0.9, "probabilities": {"false": 0.1, "true": 0.9}, "confidence": 0.9},
    "next_step": {
      "type": "choice",
      "choice": "send_replacement",
      "probabilities": {"send_replacement": 0.8, "ask_for_details": 0.15, "other": 0.05},
      "confidence": 0.44
    }
  },
  "usage": {"input_tokens": 561, "output_tokens": 0, "state_tokens": 25, "truncated": false, "images": 1, "image_tokens": 294},
  "latency_ms": …
}

The values are illustrative, not recorded output. usage.images and usage.image_tokens follow truncated on requests with images only.

Other endpoints

EndpointAuthResponse
GET /v1/modelsPublicModel IDs, aliases and context size. Retired IDs include routed_to for the current model.
GET /v1/meBearer keyKey name, per-minute and daily limits, usage and remaining quota.
GET /healthPublic200 / status: ok when ready; 503 with loading, upstream_unreachable or error otherwise.
Key and quota responseGET /v1/me
{
  "key": {"id": 3, "name": "Tomas – email triage", "prefix": "vsk_9Xk2mQ1p", "created_at": "2026-09-21T10:00:00Z", "expires_at": null},
  "limits": {"requests_per_minute": null, "requests_per_day": null},
  "usage": {"today_requests": 17, "today_remaining": null, "quota_resets_at": "2026-09-22T00:00:00Z",
            "total_requests": 231, "total_input_tokens": 88410},
  "model": "vansa-3.5"
}

Request quotas are set individually for each API key. null means unlimited; a number is the quota assigned to that key.

Question types

All answer types return probabilities and confidence. The following examples illustrate their shapes.

choice — one option

Choose from 2–255 named options. The answer’s choice is one of your keys.

// definition
"intent": {
  "type": "choice",
  "instructions": "What does the customer want in `message`?",
  "criteria": {
    "refund": "money back or a charge reversed",
    "technical_help": "a bug, outage or integration problem",
    "cancellation": "wants to cancel or downgrade",
    "other": null
  }
}
// answer
"intent": {
  "type": "choice",
  "choice": "refund",
  "probabilities": {
    "refund": 0.91, "technical_help": 0.03,
    "cancellation": 0.04, "other": 0.02
  },
  "confidence": 0.71
}

Option descriptions may be strings, null, or other JSON values. With null, the model sees only the name. An array of unique, non-empty names also works. More than 16 options requires multiple passes; see limits.

score — an ordered scale

Define 2–32 levels, lowest first. score is the expected level on a zero-based scale: Σ(level × probability).

// definition: levels from lowest to highest
"frustration": {
  "type": "score",
  "instructions": "How frustrated does the customer sound in `message`?",
  "criteria": [
    "calm and neutral",
    "concerned but civil",
    "clearly annoyed",
    "very angry or using strong language"
  ]
}
// answer: score is the expected level (0 .. N-1)
"frustration": {
  "type": "score",
  "score": 2.31,
  "legend": {"0": "calm and neutral", "1": "concerned but civil",
             "2": "clearly annoyed", "3": "very angry or using strong language"},
  "probabilities": {"0": 0.02, "1": 0.12, "2": 0.39, "3": 0.47},
  "confidence": 0.24
}

Use the expected score to sort or threshold. For the most likely discrete level, choose the highest-probability entry; rounding score can produce a different level. legend maps zero-based indices to your descriptions.

noul — probability of yes

noul is a number from 0 to 1. The optional criteria describe what true and false mean.

// definition (criteria optional)
"is_phishing": {
  "type": "noul",
  "instructions": "Is this email a phishing or scam attempt?",
  "criteria": {
    "true": "phishing, scam, or fraud",
    "false": "a legitimate email"
  }
}
// answer: noul = probability the statement holds
"is_phishing": {
  "type": "noul",
  "noul": 0.08,
  "probabilities": {"false": 0.92, "true": 0.08},
  "confidence": 0.92
}

Use a threshold of 0.5 for a yes/no decision, or a higher threshold when false positives cost more.

Confidence is not the selected option’s probability. For choice and score, it is 1 − H(p) / log(N): 0 means a uniform distribution, 1 means certainty. For noul, it is max(p, 1 − p), from 0.5 to 1.

Limits & data

Request quotas are set individually for each API key and may be unlimited. GET /v1/me returns your key's quotas; null means unlimited.

Request constraintMaximumBehavior
Questions per request32Each question is evaluated separately.
Options / score levels255 / 32Keep option descriptions short. See multi-pass behavior below.
Context per question16,384 tokensIncludes state, question, options and roughly 100 fixed instruction tokens. Over-limit requests receive 400, naming the question. No truncation.
Work per request49,152 tokensThe model reads the state once for all questions, then each question's own part and, for a question with more than 16 options, its final pass (counted at its largest possible size, at most 16,384 tokens), so this count can be larger than the billed usage.input_tokens. For example, one or two 255-option questions fit a state of up to about 15,900 tokens. Larger requests receive 400; the message gives both numbers.
Request body512 KB · 8.5 MB with imagesLarger bodies receive 413. A request with images needs a Content-Length header, and its text without the images must stay within 512 KB.
Images4 per requestPNG or JPEG, at most 4,194,304 bytes each and 6,291,456 bytes in total (decoded), each side 32–2,048 pixels. Requests with images count toward the key’s own request limits; there is no separate image limit. See Images.

Latency: allow at least 100 seconds. More questions, longer states and more than 16 options take longer; bursts may queue. Split large batches into several requests. With more than 16 choice options, groups of up to 16 are evaluated first, then the group winners enter a final pass.

Access: no SLA. Create an account and API keys in the console; keys created there use your account’s token balance. The playground has shared demo access. Keys may be rate-limited or rotated.

Data handlingStorage and processing

The public API's routine usage database does not store full inputs or answers. Usage records contain request time, key identifier, model, question count, token count, latency, status and client IP for quotas and support. Images are processed in memory to answer the request and are not stored; usage records contain no image data. Error records and diagnostic logs may contain fragments of submitted content, including question IDs or model names.

Playground drafts are saved separately in your browser. In the current deployment, requests pass through Cloudflare and are answered by our model, self-hosted in Lithuania on servers we operate; inputs are not sent to a third-party AI inference provider.

Only send data you are comfortable sharing with this API. See the Privacy Policy for storage, retention and browser data details.

Errors

Errors return a non-2xx HTTP status and an error object. type and message are always present; code and param are included when available.

{
  "error": {
    "type": "invalid_request_error",
    "message": "`state` must not be empty.",
    "param": "state"
  }
}
Statustype / codeMeaning
400invalid_request_errorMalformed JSON, missing fields, bad question definition, a question whose prompt is over 16,384 tokens, or a request that needs the model to read more than 49,152 tokens in total. param, when present, names the field; for a request over the token limit the message names the question.
400invalid_request_error / images_not_supportedImages are not available for this API key.: this key cannot send images (for example the playground’s shared demo access). Images are not available right now.: the model currently serving cannot read images; try again later. Images are not available on this model. (from the model server, without a code) means the same. The request is not processed and not charged.
400invalid_request_error / invalid_assets`assets` must be an object mapping image references to images. Also when images are sent as images or image instead of assets.
400invalid_request_error / too_many_imagesToo many images: <n> (the maximum is 4 per request).
400invalid_request_error / invalid_image_referenceImage reference '<ref>' must be 1-64 letters, digits, spaces, '.', '_' or '-'., Image reference '<ref>' is part of another reference ('<other>'); use references that do not contain each other. or Image references '<ref>' and '<other>' overlap in state.
400invalid_request_error / invalid_imageImage '<ref>': data_base64 must be plain base64 without a data: prefix., Image '<ref>': format must be png, jpg or jpeg., Image '<ref>' is not a valid PNG or JPEG file of that format., Image '<ref>' is an animated PNG; send one still image., Image '<ref>' is a CMYK JPEG; send an RGB or grayscale image., Image '<ref>': type must be image., Image '<ref>': unknown field '<name>' (use type, format, data_base64). or Image '<ref>': only data_base64 images are supported (no multipart parts).
400invalid_request_error / image_too_largeImage '<ref>' is <n> bytes; the maximum is 4194304 per image and 6291456 per request.
400invalid_request_error / image_sizeImage '<ref>' is <w> x <h> pixels; each side must be 32-2048 pixels and the image at most 4194304 pixels.
400invalid_request_error / image_unreadableImage '<ref>' could not be read. The file cannot be decoded. The model server can return the same message without a code.
400invalid_request_error / image_reference_countImage '<ref>' must appear exactly once in state (found <n>).
400invalid_request_error / image_reference_placeImage references may only appear in state (found '<ref>' in questions.<id>). or Image references may only appear in state string values (found '<ref>' in a key of <path>).
400invalid_request_error / reserved_textstate or questions contain a reserved control sequence; remove it. A request with images contains special-token text in angle brackets.
401authentication_errorMissing, invalid, revoked or expired API key.
402billing_error / insufficient_tokensConsole key only: the account’s token balance cannot cover this request’s reservation. Add prepaid credit, choose a plan or wait for your plan to renew.
402billing_error / billing_holdConsole key only: the billing account is on hold. Contact support.
403billing_error / sandbox_keyA sandbox (test-mode) key was sent to the production API. Sandbox keys cannot run production requests.
404not_found_error / model_not_foundUnknown endpoint or model name.
413invalid_request_error / request_too_largeBody over 512 KB. A request with images may be up to 8.5 MB (The request body is <n> bytes; the maximum is 8.5 MB for requests with images and 512 KB otherwise.), and its text without the images must stay within 512 KB (The request without its images is <n> bytes; the maximum is 512 KB.).
429rate_limit_error / requests_per_minutePer-minute limit hit. Wait Retry-After seconds.
429rate_limit_error / daily_quotaDaily quota used up; resets at 00:00 UTC.
503server_error / model_loading, server_busyModel starting up, too many requests queued, or too many large image uploads at once. Retry after Retry-After seconds.
502server_errorThe model server is unreachable or restarting, did not answer in time, or failed on this request. If it did not read a request’s images: The model server did not read the images; the request was not charged. Retry in a moment. Retry with backoff.
500server_errorSomething broke on our side. Send us the request id.

Retry 429, 502 and 503 with backoff; do not retry 400, 401, 402, 403, 404 or 413. API responses carry X-Request-Id; an unexpected 500 gives the id in its error message instead.

Cloudflare 403 / 1010

A 403 with the plain-text body "error code: 1010" does not come from the API but from Cloudflare's browser integrity check: it rejects the default Python-urllib User-Agent. Send any User-Agent header (for example my-app/1.0); requests, curl and fetch already do.

Additional reference

Model behavior, question-writing guidance and presets.

What's new in Vansa-3.5Model notes

Released 20 October 2026. Vansa-3.5 is the current model and the default for every model name. It uses a 16,384-token context per question.

  • Images (21 October 2026): a request can include up to 4 PNG or JPEG images in an optional assets map; image tokens count as input tokens. See Images.
  • Compatibility: requests without images use the same request and response format as before. vansa-3.4 and every earlier model name keep working and are answered by Vansa-3.5; responses say "model": "vansa-3.5". Existing TypeSafe Jev clients can switch by changing the base URL, key and model.
  • Measured: On three of our internal test sets, with the same items for both models, Vansa-3.5 answered more decisions correctly than Vansa-3.4 (accuracy averaged over each set's task groups): 78.3 % vs 63.2 % on our hard decision set; 92.9 % vs 89.1 % on never-seen tasks (questions written for our own tests and never published); 72.6 % vs 69.8 % on held-out public classification tasks. Validate the results on your own inputs.
  • Instructions inside the state: put the rules your application relies on in instructions or criteria, not inside the state.
  • Email threads: the email preset's questions and state builder are unchanged. Validate the answers on your own threads.
  • Token counts: Vansa-3.5 counts tokens with a different tokenizer than Vansa-3.4, so usage.input_tokens for the same request changes with the language and content of the state. In our tests: +4 % for English email text, -1 % for German, +18 % for URLs and log lines and +24 % for Chinese text. A state that just fitted 16,384 tokens per question may now receive 400.
  • Work per request: one request may need the model to read at most 49,152 tokens (see the limits); a larger request receives 400. Use client timeouts of at least 100 seconds and split large batches into several requests.
Use casesWhen to use the API

Use Vansa for classification, ordered scoring and yes/no decisions: ticket routing, product feedback, intent detection, content checks, urgency or sentiment scoring, LLM input checks, and selecting an agent’s next action.

Inference is deterministic for the same request, with no generation temperature to set. Answers stay within your defined options. Accuracy varies by task; evaluate on your own inputs.

Writing questionsInput guidance
  • Name state fields. Send a JSON object and refer to fields in instructions, such as “Which team should handle the request in body?”
  • Describe ambiguous options. Include other or none of the above when the categories are not exhaustive.
  • Ask only relevant questions. Batching saves round trips, but each extra question still adds time.
  • Keep relevant context. Remove signatures, footers and unnecessary quoted history. The prompt limit is 16,384 tokens.
  • Use English. Use English for the documented examples. Validate other languages on representative inputs before relying on the results.
Email qualification presetOutreach workflow

26 fixed questions for guest-post and link-building outreach, plus 26 extras. Use the published question wording and state builder; the questions are asked from the buyer’s perspective.

FileWhat it is
/presets/email-qualification.jsonThe preset: questions (send as is), extra_questions, the state format, an example state and usage rules.
/presets/email_state.pyReference state builder, Python standard library only: cleans text, computes counts, keeps up to eight messages and trims the state to an estimated 1,250-token budget. The built-in token estimate is approximate and can exceed the target; provide a compatible token counter when exact sizing is required.
/llms.txtThe same guidance for AI coding agents, in plain text.
State format and preprocessingOne outreach thread
{
  "site": "example-garden-blog.com",
  "subject": "Guest post on example-garden-blog.com",
  "roles": "us = our outreach team buying a sponsored guest post; them = the website owner or editor",
  "counts": {
    "ours": 2,
    "theirs": 1,
    "auto_replies": 0,
    "bounces": 0,
    "days_since_last": 112,
    "last_from": "us"
  },
  "messages": [
    {
      "n": 1,
      "from": "us",
      "date": "2026-06-02",
      "text": "Hi, we would like to publish a sponsored guest post on your site for one of our clients. Do you accept guest posts, and what is your price for one article with one do-follow link?"
    },
    {
      "n": 2,
      "from": "them",
      "date": "2026-06-03",
      "text": "Hello, our price is 150 USD per post, do-follow and permanent. Payment in advance by PayPal."
    },
    {
      "n": 3,
      "from": "us",
      "date": "2026-06-04",
      "text": "Thanks, we will check with our client and get back to you."
    }
  ]
}
  • us is always the outreach side (the buyer of the post), them the website. Keep roles exactly as shown.
  • counts are computed by your code from the mail headers and today's date: theirs excludes auto-replies and bounces, auto_replies counts every message with an auto-reply signal (bounces that carry one included), days_since_last is whole days since the last message, last_from is who sent it. The time-based outcomes (still open or ghosted after 14 days) depend on them.
  • Messages: oldest first, date as YYYY-MM-DD, text is the new text only (quoted replies, > lines, mail footers and anything after a line of only 2 or 3 dashes or a "Sent from my iPhone/iPad/Samsung/Android" line removed), at most its first 150 words, shortened to the first 120 plus up to 3 later sentences (35 words at most) that carry an amount or a yes/no answer, with " ..." where text was cut (fewer words when a long thread must be trimmed), plus "kind": "auto_reply" or "bounce" when it applies.
  • Long threads keep the first message, their first real reply and the last 4, with {"n": "...", "omitted": 7} in place of the rest; the whole state stays under about 1,250 tokens. email_state.py does all of this.
  • Send the state as a JSON object with the keys in this order, not as a string: the model reads it serialised exactly like that.
Request and example answersPython
# pip install requests; email_state.py from https://docs.vansa.org/presets/email_state.py
import os
import requests
from email_state import build_state

VANSA_KEY = os.environ["VANSA_KEY"]  # your API key (vsk_...)
preset = requests.get("https://docs.vansa.org/presets/email-qualification.json", timeout=30).json()
state = build_state(
    site="example-garden-blog.com",
    subject="Guest post on example-garden-blog.com",
    emails=[  # every email with this site; "us" = your outreach team
        {"from": "us",   "date": "2026-06-02T09:00:00+00:00", "text": "Hi, we would like to publish a sponsored guest post ..."},
        {"from": "them", "date": "2026-06-03T10:00:00+00:00", "text": "Hello, our price is 150 USD per post, do-follow and permanent. ..."},
        {"from": "us",   "date": "2026-06-04T09:00:00+00:00", "text": "Thanks, we will check with our client and get back to you."},
    ],
)
r = requests.post(
    "https://api.vansa.org/v1/systemone",
    headers={"Authorization": f"Bearer {VANSA_KEY}"},
    json={"model": preset["model"], "state": state, "questions": preset["questions"]},
    timeout=100,
)
r.raise_for_status()
answers = r.json()["answers"]
print(answers["outcome"]["choice"], answers["mistake_gave_up_too_early"]["noul"])

Recorded Vansa-3 answers for the example thread above (the site quoted, we promised to come back and never did):

QuestionAnswerProbability
replyanswered1.00
outcomeghosted_by_us1.00
negotiationprice_named_no_decision1.00
fail_reasonwe_dropped1.00
they_named_pricetrue1.00
rounds0 (0, we never sent a number)1.00
prepayment_demandedyes0.99
our_handlingok0.98
mistake_gave_up_too_earlytrue0.93
Preset questions26 questions

Exact wording and option descriptions are in the preset file; send them unchanged.

IdTypeAnswersWhat it tells you
replychoiceno_reply · auto_reply · bounce · answereddid the site answer at all, automatically, or bounce
outcomechoicepublished · agreed_not_published · declined_by_them · declined_by_us · ghosted_by_them · ghosted_by_us · still_open · no_response · unclearwhere the deal ended: published, agreed, declined, ghosted (either side), still open
negotiationchoiceno_price_talk · price_named_no_decision · accepted_first_price · negotiated_success · negotiated_fail · firm_price · they_accepted_ours · countered_no_decisionhow the price talk went
fail_reasonchoicenot_applicable · none_yet · price_too_high · niche_rejected · no_dofollow · sponsored_tag_only · not_permanent · off_script_terms · no_response · wrong_contact · we_dropped · payment_problem · otherthe main reason it was not published
they_named_pricenoultrue / falsedid they quote a per-post price
price_for_igamingchoicenot_mentioned · same_price · different_pricea separate price for betting / casino content
roundsscore0: 0, we never sent a number · 1: 1 number · 2: 2 numbers · 3: 3 or more numbershow many different amounts we proposed
dofollowchoiceyes · no · unknowndo-follow links confirmed by them
permanentchoiceyes · no · unknownthe post stays up for good
sponsored_tag_requiredchoiceyes · no · unknowna sponsored / partnership label is required
prepayment_demandedchoiceyes · no · unknownpayment before publishing
article_by_them_onlychoiceyes · no · unknownthey insist on writing the article
links_allowedchoicenot_said · one · two · three_or_morelinks allowed in one post
our_handlingchoicegood · ok · mistakehow well our side handled the thread
mistake_answered_twicenoultrue / falseour mistake: answered twice
mistake_ignored_their_questionnoultrue / falseour mistake: ignored their question
mistake_wrong_pricenoultrue / falseour mistake: wrong price
mistake_too_pushynoultrue / falseour mistake: too pushy
mistake_gave_up_too_earlynoultrue / falseour mistake: gave up too early
mistake_accepted_too_fastnoultrue / falseour mistake: accepted too fast
mistake_wrong_languagenoultrue / falseour mistake: wrong language
mistake_revealed_client_earlynoultrue / falseour mistake: revealed client early
mistake_promised_paymentnoultrue / falseour mistake: promised payment
mistake_sent_wrong_detailsnoultrue / falseour mistake: sent wrong details
mistake_slow_to_answernoultrue / falseour mistake: slow to answer
mistake_othernoultrue / falseour mistake: other
Extra questionsSecond request

26 additional questions, in extra_questions. Ask them in a second request with the same state (26 + 26 is over the 32-question limit). The "Use answer when" column gives the applicability conditions (extra_questions_only_when in the preset). Ignore an answer when its condition does not hold.

IdTypeAnswersUse answer when
last_fromchoiceus · themalways
replies_bucketscore0: none · 1: one · 2: two or three · 3: four or morealways
thread_agechoiceunder_2_weeks · 2_to_8_weeks · over_8_weeksalways
payment_methodchoicenone · paypal · bank_transfer · card · crypto · wise · otheralways
invoice_mentionednoultrue / falsealways
deadline_mentionednoultrue / falsealways
word_count_bucketchoicenot_said · under_500 · 500_to_999 · 1000_or_moreonly when counts.theirs > 0
niche_restrictions_mentionednoultrue / falsealways
tone_of_themchoicefriendly · neutral · cold · hostileonly when counts.theirs > 0
tone_of_uschoicefriendly · neutral · pushyalways
they_asked_question_lastnoultrue / falsealways
we_promised_to_returnnoultrue / falsealways
discount_offerednoultrue / falsealways
package_offernoultrue / falsealways
language_mismatchnoultrue / falsealways
their_last_topicchoiceprice · terms · article_requirements · scheduling · rejection · question · confirmation · payment · otheronly when counts.theirs > 0
they_requested_somethingnoultrue / falsealways
we_sent_articlenoultrue / falsealways
published_url_sharednoultrue / falsealways
they_asked_payment_proofnoultrue / falsealways
distinct_prices_bucketscore0: none · 1: one · 2: two · 3: three or morealways
price_bandscore0: no price named · 1: under 50 · 2: 50 to 99 · 3: 100 to 199 · 4: 200 to 499 · 5: 500 or moreonly when counts.theirs > 0
currencychoicenone · EUR · USD · GBP · otheronly when counts.theirs > 0
price_came_downnoultrue / falseonly when they named a price and a final price was agreed
they_lowered_pricenoultrue / falseonly when they named a price
igaming_costs_morenoultrue / falseonly when they named both a normal and an iGaming price
Preset limitationsAccuracy, language and perspective
  • No amounts or text. Prices, counters, the final price, a suggested next reply and free-text notes are not answered: extract numbers with code or an LLM. they_named_price, rounds and the price extras cross-check such an extraction.
  • Review uncertain answers. Use confidence to send doubtful threads to a person.
  • Fresh threads. Check low-confidence answers on threads that are still moving.
  • Language. Use English for the documented examples. Validate other languages on representative inputs before relying on the results.
  • Perspective. The published questions use the buyer's perspective. Validate changes in perspective or roles on your own inputs.