Documentation
Decisions with an honest probability for every option, on text, images and recordings.
Quick start
- Base URL
- https://api.parallelfoundry.com/v1
- Model
- parallel-foundry/parallel
- Key
- header Authorization: Bearer pf-... (or X-API-Key); make one on Keys in the console
- Price
- $0.025 per million input tokens; output is free; no minimum per call (how tokens are counted)
curl https://api.parallelfoundry.com/v1/decide \
-H "Authorization: Bearer $PARALLEL_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "My card was charged twice, please refund one payment.",
"heads": ["intent=refund,card lost,balance", "urgent=bool", "frustration=score:1-5"]
}'{
"intent": {"type": "choice", "answer": "refund",
"probabilities": {"refund": 0.9942, "balance": 0.0037, "card lost": 0.0021}},
"urgent": {"type": "bool", "answer": "no", "p_yes": 0.3758},
"frustration": {"type": "score", "answer": "2", "expected": 2.263,
"probabilities": {"2": 0.3698, "1": 0.2707, "3": 0.2189, "4": 0.1072, "5": 0.0334}},
"usage": {"input_tokens": 76, "output_tokens": 0,
"input_tokens_detail": {"decision": 76, "media": 0, "writing": 0, "unread": 0}},
"seconds": 0.021
}POST /v1/decide
One request: an input (a text, images, a recording, or a text with them) and any number of questions about it, all answered in one pass. The questions can be strings or objects.
| Field | What |
|---|---|
| text | the text to decide on |
| heads | the questions. Strings: name="a,b,c" (one of the options), name=bool (yes/no), name=score:1-5 or name=score:"low,medium,high" (a scale), name=multi:"a,b,c" (every option that applies), name=free:"Question?" (no options: Parallel proposes them). Objects: {name, type, question, options} with type choice, bool, score or multi. Names are read as words (refund_request = "refund request"); usage and seconds are taken by the answer |
| question | with options: one choice question written out |
| free | a question (or a list of up to 3) without options: Parallel proposes the answers and gives them % |
| reason | true: a short written explanation of every answer (text only, up to 8 questions) |
| tools | a list of tools (name, description, JSON-schema parameters; plain or OpenAI-style): Parallel picks one, or "none of these tools", with %, and writes its arguments. tool_choice: auto (default), required, or {"type": "function", "function": {"name": ...}} |
| images | up to 4, each a base64 data URL (JPEG, PNG, WebP, GIF, BMP); links are not fetched |
| image_tokens | the detail of the images: 70, 140, 280 (default), 560 or 1120. More detail reads finer print and costs more (pricing); photos do well at 70-140, documents at 280-560 |
| audio | recordings, each a base64 data URL (WAV, FLAC or OGG, any sample rate), up to 60 seconds in all |
{
"text": "The food was great but we waited 40 minutes.",
"heads": [
{"name": "sentiment", "type": "choice", "question": "overall sentiment", "options": ["positive", "mixed", "negative"]},
{"name": "urgent", "type": "bool", "question": "Is it urgent?"},
{"name": "rating", "type": "score", "question": "rating", "options": ["1", "2", "3", "4", "5"]},
{"name": "aspects", "type": "multi", "question": "aspects", "options": ["food", "service", "price", "wait"]}
],
"reason": true
}{
"images": ["data:image/jpeg;base64,/9j/4AAQ..."],
"image_tokens": 280,
"heads": ["type=invoice,receipt,letter,form", "amount_over_300=bool"]
}
{
"audio": ["data:audio/wav;base64,UklGR..."],
"heads": ["intent=card issues,balance,pay bill,other"]
}{
"text": "Can you book a table for four tomorrow at 7:30 pm at Luma Bistro?",
"tools": [
{"name": "book_table", "description": "Book a restaurant table",
"parameters": {"type": "object", "properties": {"restaurant": {"type": "string"},
"people": {"type": "integer"}, "time": {"type": "string"}}, "required": ["restaurant", "people"]}},
{"name": "get_weather", "description": "Current weather in a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}
],
"tool_choice": "auto"
}Answers
One entry per question, under its name, and the call's usage:
| Type | Fields |
|---|---|
| choice | answer (the likeliest option), probabilities (every option, summing to 1) |
| bool | answer ("yes" or "no"), p_yes |
| score | answer, probabilities, expected (the average on a number scale; expected_index on a word scale) |
| multi | answer (every option at 50% or more), probabilities (each its own yes) |
| tool | answer (the tool, or null for none), probabilities, arguments (only what the tool's schema declares, in its types; anything missing is listed under problems, never invented) |
| with reason | also reason: a sentence or two on why |
| proposed (free) | also options_proposed: true |
| usage | input_tokens (what you pay for), output_tokens (free), and input_tokens_detail: decision (the text and questions read), media (images and recordings), writing (reasons, proposed options, tool arguments), unread (sent but not read) |
OpenAI-compatible: POST /v1/chat/completions
The OpenAI SDKs work with base_url="https://api.parallelfoundry.com/v1". Two kinds of call:
- Decisions: response_format json_schema whose fields are enums, booleans, integer ranges (2 to 11 values) or arrays of enums. Every field is decided in one pass; the content is that JSON, always valid, and decisions holds every option's %. A field's description is its question. Images (image_url, base64) and recordings (input_audio) work too.
- Tool calls: tools and tool_choice: the tool is picked with %, its arguments written; when no tool fits, the answer is empty with each tool's %.
Free-form answers (plain chat, written JSON, the answer after a tool result) are not offered: they get 400 chat_not_offered. stream: true works (with stream_options.include_usage). GET /v1/models lists the model; GET /v1/key shows your key's credit and limits.
from openai import OpenAI
client = OpenAI(base_url="https://api.parallelfoundry.com/v1", api_key="pf-...")
r = client.chat.completions.create(
model="parallel-foundry/parallel",
messages=[{"role": "user", "content": "I was charged twice, please refund one payment now!"}],
response_format={"type": "json_schema", "json_schema": {"name": "ticket", "schema": {
"type": "object", "properties": {
"intent": {"type": "string", "enum": ["refund", "cancel order", "order status", "complaint"]},
"urgent": {"type": "boolean"},
"frustration": {"type": "integer", "minimum": 1, "maximum": 5}}}}})
print(r.choices[0].message.content) # {"intent": "refund", "urgent": true, "frustration": 4}
print(r.model_dump()["decisions"]) # every option's probabilityPricing: how input tokens are counted
$0.025 per million input tokens. Output is free and there is no minimum per call. Input is everything you send and everything the model reads to answer it, at every step:
| Input | Counts as | Costs |
|---|---|---|
| Text | the tokens read: about 1.44 tokens a word of English (1,000 words: about 1,440) | $0.000036 per 1,000 words |
| Questions and options | as read (a question with three options: about 15-30 tokens) | |
| An image at detail 70 | 1,000 tokens, whatever its file size (every image is resized to its detail) | $0.000025 |
| An image at detail 140 | 1,700 tokens, whatever its file size (every image is resized to its detail) | $0.0000425 |
| An image at detail 280 (default) | 3,800 tokens, whatever its file size (every image is resized to its detail) | $0.000095 |
| An image at detail 560 | 8,000 tokens, whatever its file size (every image is resized to its detail) | $0.0002 |
| An image at detail 1120 | 22,000 tokens, whatever its file size (every image is resized to its detail) | $0.00055 |
| A recording | 160 tokens a second (30 s: 4,800) | $0.00024 a minute |
| Steps that write (reasons, proposed options, tool arguments) | what they read (the text again, with their instruction), counted 3 times: writing takes far longer than deciding, as a model that thinks longer costs more. What they write is free | |
| Sent but not read | counted as sent: the tools not picked, system messages in a decision, a text past the 10,000-token limit |
Every answer says what it counted (usage.input_tokens_detail); the console's Usage page shows every call.
Limits
| A request | at most 10 MB as sent (base64 files included), 10,000 tokens read (a longer text is cut, its rest still counted), 32 questions of up to 200 options |
| Images | 4 a request, each at most 50 megapixels |
| Recordings | 60 seconds a request in all |
| Written parts | reasons with up to 8 questions (a reason at most 80 tokens), proposed options for up to 3, tool arguments up to 256 tokens |
| Per key (default) | 50 calls a second, 2,500 a minute, 5,000,000 input tokens a minute; with images or recordings 20 a second; written parts 10 a second and 6,000 written tokens a minute; 200 at once. Need more? Ask us |
| Busy | when every GPU is full the answer is 429 at once, with Retry-After: nothing waits in a queue |
Errors
Errors are {"error": {"message", "type", "code"}}. Refused calls are never charged.
| Status | When |
|---|---|
| 400 | a bad request (the message says what): a malformed question, a file that cannot be read, a reserved name; chat_not_offered: a free-form answer |
| 401 | no key, a wrong key, or a revoked one (within 30 seconds of revoking) |
| 402 | no credit left: add credit |
| 404 | another model or path |
| 413 | too large: the 10 MB, the 10,000 tokens before the text, an image over 50 megapixels, recordings over 60 seconds |
| 429 | a limit of your key, or every GPU busy (with Retry-After) |
| 503 | the model is starting or unreachable: try again after Retry-After |
Privacy
No text, image, recording or answer is stored, logged or used for training. For each call we keep its time, key, kind and structure (which inputs, how many questions of which type, reasons on or off), tokens, cost, status and the country it came from, for billing and your call history (the console shows 30 days).
Questions: hello@parallelfoundry.com. The console's playground shows the exact request for every query (curl, Python, Node.js).