# Account Settings
Source: https://docs.sciforium.com/account-settings
| Setting | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | View and edit your full name, including prefix, first, middle, last, and suffix fields. Click Edit next to the Name section to update your display name. |
| **Email Address** | View your email address. Changes require verification. Contact support if you need to update your login email. |
| **Organization** | View your organization affiliation. |
| **User Type** | View your user role or type. |
## Deleting Your Account
Account deletion is permanent and irreversible. All data including API keys, usage history, and billing records is removed. Any remaining credit balance is forfeited.
To delete your account:
1. Go to **Settings** → **Account Management** → **Delete**
2. Confirm when prompted
If you are the sole Admin of an organization, you must transfer ownership or delete the organization before deleting your account.
# Due Diligence
Source: https://docs.sciforium.com/agentic-due-dillegince
# Building a deep agentic document-understanding system on Sciforium
This notebook is a hands-on tour. By the end you will have built, stage by stage, an investor-grade due-diligence pipeline that takes a PDF and returns a cited, fact-checked memo (plus a podcast and a cover image).
Everything is defined inline — no project imports. Each stage uses one primitive of the Sciforium API. The same pattern underlies most useful production agent pipelines: **cheap model ×N in parallel → mid-tier model ×M in parallel → best model ×1 for synthesis.**
```md theme={null}
PDF --> parse --> extract (×5 parallel) --> ground (Exa) --> verify (batched) --> analyze (×6 parallel) --> synthesize --> memo + audio + image
```
## What you'll learn
| Primitive | Endpoint | Where it shows up |
| ------------------------ | -------------------------------- | -------------------------------- |
| `chat()` | `POST /v1/chat/completions` | Every LLM call |
| `chat_with_attachment()` | same, with a `file` content part | Metrics extraction, verification |
| `parse_file()` | `POST /api/attachments/parse` | Turning a PDF into text |
| `synthesize_speech()` | `POST /v1/audio/speech` | Podcast TTS |
| `generate_image()` | `POST /v1/images/generations` | Cover image |
| Agentic pattern | Stage |
| ---------------------------------------------------------------------------------- | ------------------------------- |
| Many small LLM calls in parallel | Extraction, analysis |
| Bounded concurrency (semaphore) around a third-party API | Exa grounding |
| Batching work so a big task streams progress | Verification |
| Evidence fusion — feeding the output of cheap calls into a best-in-class synthesis | Memo |
| Schema-constrained JSON output with robust parsing | Verification, podcast scripting |
| Plan → fan-out generate → stitch | Podcast |
## Prerequisites
* A Sciforium API key in `.env` as `SCIFORIUM_API_KEY`.
* Optional: `EXA_API_KEY` for web grounding.
* `pip install openai httpx` (already in this project's venv).
## 0 · Setup
Load environment variables, set base URLs, and sanity-check the key. No pipeline imports — everything from here down is written in the notebook.
```python theme={null}
import asyncio, base64, contextvars, json, os, wave
from pathlib import Path
import httpx
from openai import AsyncOpenAI
from IPython.display import Markdown, display
def load_dotenv(path='.env'):
if not Path(path).exists():
return
for line in Path(path).read_text().splitlines():
line = line.strip()
if not line or line.startswith('#') or '=' not in line:
continue
k, _, v = line.partition('=')
os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
load_dotenv()
API_KEY = os.environ.get('SCIFORIUM_API_KEY', '')
BASE_URL = os.environ.get('SCIFORIUM_BASE_URL', 'https://api.sciforium.com').rstrip('/')
EXA_KEY = os.environ.get('EXA_API_KEY', '')
assert API_KEY, 'Set SCIFORIUM_API_KEY in .env before continuing.'
print(f'API base : {BASE_URL}')
print(f'API key : ****{API_KEY[-4:]}')
print(f'Exa key : {"set" if EXA_KEY else "not set (web grounding will be skipped)"}')
```
## 1 · Configuration — models and tasks
The whole pipeline is parameterised by a `MODELS` dict and two task lists. This is the only place you choose *capability vs. cost per stage*.
Rule of thumb:
* **Extractor** — cheap and fast. You'll call it many times in parallel.
* **Verifier** — mid-tier. We batch the work so throughput > single-call capability.
* **Analyst** — mid/strong. Fewer calls, each needs to reason across evidence.
* **Synthesizer** — best model you have. One call, highest-stakes output.
```python theme={null}
MODELS = {
'extractor': 'openai/gpt-oss-120b',
'verifier': 'openai/gpt-oss-120b',
'analyst': 'openai/gpt-oss-120b',
'synthesizer': 'openai/gpt-oss-120b',
'tts': 'Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice',
'image': 'tencent/HunyuanImage-3.0-Instruct',
}
EXTRACTION_TASKS = [
# (name, prompt, attach_original_file)
('summary', 'Summarise this document in exactly 3 sentences.', False),
('key_points', 'List the 5 most important claims or findings. One per line, no bullets.', False),
('metrics', "Extract every number, percentage, monetary amount, date, or financial data point. "
"Format strictly as one per line: 'METRIC: — CONTEXT: '. "
"Be exhaustive. Preserve units exactly.", True),
('entities', "List every person, company, product, and date mentioned. For people tag the role "
"(founder/investor/customer/advisor/etc). One per line. Format: 'NAME — ROLE'.", False),
('risks', 'List any risks, caveats, or open questions the document raises. One per line.', False),
]
ANALYSIS_TASKS = [
('market', 'Assess the market opportunity. Are TAM/SAM/SOM claims credible against the web evidence?'),
('team', 'Assess the founding team. Use web evidence to check backgrounds and red flags.'),
('moat', 'Assess competitive moat. Who are the real competitors based on web evidence?'),
('economics', 'Assess unit economics. Flag any metric the verification layer marked UNVERIFIED or CONTRADICTED.'),
('risks', 'Synthesise the most material risks. Rank by severity.'),
('assessment', 'One paragraph — what would need to be true for this to be a strong investment?'),
]
for tier, model in MODELS.items():
print(f' {tier:<14} {model}')
```
## 2 · Primitive — `chat()` one-shot completion
Sciforium speaks the OpenAI Chat Completions protocol. Anything that works with `openai-python` against OpenAI works here — just point `base_url` at `https://api.sciforium.com/v1`.
We use `AsyncOpenAI` so downstream stages can fan out with `asyncio.gather`.
```python theme={null}
async def chat(model: str, system: str, user: str) -> str:
client = AsyncOpenAI(api_key=API_KEY, base_url=BASE_URL + '/v1')
response = await client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': system},
{'role': 'user', 'content': user},
],
)
return response.choices[0].message.content.strip()
# Smoke test — make sure our key works before we build anything on top.
reply = await chat(
MODELS['extractor'],
'You are terse.',
'In five words: what is due diligence?',
)
print(reply)
```
## 3 · Primitive — `chat_with_attachment()` multimodal file input
When numeric fidelity matters — tables, figures, dense financials — a lossy text parse is a bad input. Sciforium's chat endpoint accepts a `file` content part with a base64 data URL. The gateway extracts native bytes before the model sees the message.
We'll use this in two places: the `metrics` extraction task, and the whole verification stage.
```python theme={null}
MIME_MAP = {
'pdf': 'application/pdf',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'doc': 'application/msword',
'txt': 'text/plain',
'md': 'text/markdown',
'csv': 'text/csv',
'png': 'image/png',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
}
async def chat_with_attachment(model: str, system: str, user: str, file_path: str) -> str:
path = Path(file_path)
mime = MIME_MAP.get(path.suffix.lstrip('.').lower(), 'application/octet-stream')
data_url = f'data:{mime};base64,{base64.b64encode(path.read_bytes()).decode()}'
client = AsyncOpenAI(api_key=API_KEY, base_url=BASE_URL + '/v1')
response = await client.chat.completions.create(
model=model,
messages=[
{'role': 'system', 'content': system},
{'role': 'user', 'content': [
{'type': 'text', 'text': user},
{'type': 'file', 'file': {'filename': path.name, 'file_data': data_url}},
]},
],
)
return response.choices[0].message.content.strip()
```
## 4 · Primitive — `parse_file()` layout-aware parser
`chat_with_attachment` is great for a single question about one doc, but for multi-stage pipelines you usually want plain text *once* — otherwise every extraction step re-parses the same file on the server.
Sciforium's `/api/attachments/parse` endpoint returns per-page structured text with layout preserved. Call it once, cache the output, reuse for every text-only stage.
```python theme={null}
async def parse_file(file_path: str) -> str:
path = Path(file_path)
mime = MIME_MAP.get(path.suffix.lstrip('.').lower(), 'application/octet-stream')
encoded = base64.b64encode(path.read_bytes()).decode()
async with httpx.AsyncClient(timeout=300) as client:
response = await client.post(
BASE_URL + '/api/attachments/parse',
headers={
'Content-Type': 'application/json',
'Authorization': f'Bearer {API_KEY}',
'x-api-key': API_KEY,
},
json={'files': [{
'url': f'data:{mime};base64,{encoded}',
'filename': path.name,
'media_type': mime,
}]},
)
response.raise_for_status()
content = response.json()['results'][0].get('content') or {}
pages = content.get('pages') or []
if pages:
return '\n\n'.join(f"[Page {p['page']}]\n{p['text']}" for p in pages if p.get('text', '').strip())
return content.get('text', '')
```
## 5 · Pick a document
Set `DOC_PATH` to your PDF. The fallback below grabs the newest PDF in `jobs/` if you've already run something through the web UI.
```python theme={null}
DOC_PATH = None # e.g. 'docs/my_deck.pdf'
FOCUS = 'the most important findings and risks for an investment decision'
if DOC_PATH is None:
candidates = sorted(Path('jobs').glob('*/*.pdf'), key=lambda p: p.stat().st_mtime, reverse=True)
if not candidates:
raise RuntimeError('No PDF found under jobs/. Set DOC_PATH to a document path.')
DOC_PATH = str(candidates[0])
print(f'Document : {DOC_PATH}')
print(f'Size : {Path(DOC_PATH).stat().st_size // 1024} KB')
document_text = await parse_file(DOC_PATH)
print(f'\nExtracted {len(document_text):,} characters.\n')
print(document_text[:1200])
```
## 6 · Stage 1 — Parallel extraction (the many-small-calls pattern)
Five tasks, fired simultaneously with `asyncio.gather`. Because each task is a separate HTTP request, the wall-clock cost is dominated by the slowest one — not the sum.
Notice the `use_attachment=True` flag on the `metrics` task. For that one call we skip our own parse output and send the original PDF — the gateway's native extractor preserves table numerics better than anything a general parser does with a bag of words.
```python theme={null}
async def run_extractions(document_text: str, file_path: str) -> dict:
model = MODELS['extractor']
system = 'You are a precise document analyst. Extract only what is asked. Be concise and exhaustive.'
async def one(name: str, prompt: str, use_attachment: bool):
if use_attachment:
result = await chat_with_attachment(model, system, prompt, file_path)
tag = ' [attached]'
else:
user = f'\n{document_text}\n\n\n{prompt}'
result = await chat(model, system, user)
tag = ''
print(f' ✓ {name}{tag}')
return name, result
print(f'[Extract] {len(EXTRACTION_TASKS)} tasks in parallel (model: {model})')
pairs = await asyncio.gather(*[one(n, p, a) for n, p, a in EXTRACTION_TASKS])
return dict(pairs)
extractions = await run_extractions(document_text, DOC_PATH)
for name, text in extractions.items():
display(Markdown(f'### {name}\n\n{text}'))
```
## 7 · Stage 2 — Grounding with Exa (bounded concurrency)
For every top claim and every named person we want external corroboration. Two rules:
1. **Fan out** — queries are independent, so run them with `asyncio.gather`.
2. **Bound the fan** — Exa (like any third-party API) rate-limits aggressive callers. A shared `asyncio.Semaphore` caps how many requests are in flight. 4 is a safe default for free/basic tiers.
This is a pattern you'll use any time you chain an LLM stage into an external API.
```python theme={null}
_EXA_SEMAPHORE = asyncio.Semaphore(4)
async def exa_search(query: str, num_results: int = 3) -> list:
async with httpx.AsyncClient(timeout=30) as client:
response = await client.post(
'https://api.exa.ai/search',
headers={'x-api-key': EXA_KEY, 'Content-Type': 'application/json'},
json={
'query': query,
'numResults': num_results,
'useAutoprompt': True,
'contents': {'text': {'maxCharacters': 800}},
},
)
response.raise_for_status()
return [
{'title': r.get('title', ''), 'url': r.get('url', ''), 'snippet': (r.get('text') or '').strip()}
for r in response.json().get('results', [])
]
async def run_grounding(extractions: dict, num_results: int = 3, max_queries: int = 8) -> dict:
if not EXA_KEY:
print('[Grounding] Skipped — EXA_API_KEY not set.')
return {}
queries = []
for line in extractions.get('key_points', '').splitlines():
if line.strip():
queries.append(('claim', line.strip()))
for line in extractions.get('entities', '').splitlines():
line = line.strip()
if not line:
continue
name = line.split('—', 1)[0].strip() if '—' in line else line
role = (line.split('—', 1)[1].lower() if '—' in line else '')
if any(r in role for r in ('founder', 'ceo', 'cto', 'exec', 'chief')):
queries.append(('person', f'{name} background prior company'))
queries.append(('person', f'{name} litigation controversy'))
else:
queries.append(('entity', name))
queries = queries[:max_queries]
print(f'[Grounding] {len(queries)} queries (max 4 in flight, {num_results} results each)')
async def one(kind: str, q: str):
async with _EXA_SEMAPHORE:
try:
results = await exa_search(q, num_results=num_results)
except Exception as e:
print(f' ! [{kind}] {q[:50]} ({type(e).__name__})')
return q, []
print(f' ✓ [{kind}] {q[:56]}')
return q, results
return dict(await asyncio.gather(*[one(k, q) for k, q in queries]))
grounding = await run_grounding(extractions, num_results=3, max_queries=8)
for q, hits in list(grounding.items())[:3]:
print(f'\nQUERY: {q}')
for r in hits:
print(f' - {r["title"][:80]}')
print(f' {r["url"]}')
```
## 8 · Stage 3 — Batched verification
We now ask the `verifier` model to re-check every extracted claim and metric against the original document. The naive implementation is **one giant call with all 40 items** — which turns into a multi-minute silent request that users abandon.
The fix is a pattern worth remembering: **split the work into batches, fire the batches in parallel, log progress per batch.** The user sees steady motion and the provider can serve the batches independently. Same total work, much better UX.
We also need to coax the model into returning JSON reliably. `parse_json_response` is a tiny utility that handles the three common failure modes (markdown fences, prose prelude, partial braces).
````python theme={null}
def parse_json_response(text: str):
text = (text or '').strip()
if '```' in text:
for part in text.split('```'):
part = part.strip()
if part.startswith('json'):
part = part[4:].lstrip()
if part.startswith(('{', '[')):
try:
return json.loads(part)
except json.JSONDecodeError:
continue
try:
return json.loads(text)
except json.JSONDecodeError:
pass
for opener, closer in (('{', '}'), ('[', ']')):
start, end = text.find(opener), text.rfind(closer)
if start != -1 and end > start:
try:
return json.loads(text[start:end + 1])
except json.JSONDecodeError:
continue
raise ValueError(f'Could not parse JSON: {text[:200]}')
async def run_verification(extractions: dict, file_path: str, batch_size: int = 8) -> dict:
model = MODELS['verifier']
claims = []
for line in extractions.get('key_points', '').splitlines():
if line.strip():
claims.append({'kind': 'claim', 'text': line.strip()})
for line in extractions.get('metrics', '').splitlines():
if line.strip():
claims.append({'kind': 'metric', 'text': line.strip()})
if not claims:
print('[Verify] Nothing to check.')
return {'verdicts': [], 'counts': {}}
batches = [claims[i:i + batch_size] for i in range(0, len(claims), batch_size)]
print(f'[Verify] {len(claims)} items in {len(batches)} parallel batches (model: {model})')
system = ('You are a meticulous fact-checker. For each claim or metric, verify it against the '
'attached document. Respond ONLY with valid JSON — no prose, no fences.')
async def one(idx: int, batch: list):
user = (
'For each item below, mark status as one of: verified, partial, unverified, contradicted. '
'If present, quote the supporting passage in `evidence` (≤200 chars). '
'For metrics, require exact numeric match — approximations are "partial".\n\n'
'Return a JSON array:\n'
'[{"text": "", "kind": "claim|metric", "status": "...", "evidence": "..."}]\n\n'
f'Items:\n{json.dumps(batch, indent=2)}'
)
try:
raw = await chat_with_attachment(model, system, user, file_path)
parsed = parse_json_response(raw)
if not isinstance(parsed, list):
parsed = []
except Exception as e:
print(f' ! batch {idx+1}/{len(batches)} failed: {e}')
return []
print(f' ✓ batch {idx+1}/{len(batches)} ({len(parsed)} verdicts)')
return parsed
results = await asyncio.gather(*(one(i, b) for i, b in enumerate(batches)))
verdicts = [v for batch in results for v in batch]
counts = {}
for v in verdicts:
s = v.get('status', 'unknown')
counts[s] = counts.get(s, 0) + 1
return {'verdicts': verdicts, 'counts': counts}
verifications = await run_verification(extractions, DOC_PATH)
print('\nCounts:', verifications['counts'])
for v in verifications['verdicts'][:8]:
print(f" [{v.get('status','?'):<12}] {v.get('text','')[:90]}")
````
## 9 · Stage 4 — Analysis with evidence fusion
Every analyst task sees the same context packet: extractions + verification verdicts + numbered web evidence + the investor's focus. The system prompt tells the model to *downgrade confidence* on anything the verifier marked `UNVERIFIED` or `CONTRADICTED` — that's how fact-checking actually propagates into reasoning.
All six analyses run in parallel.
```python theme={null}
async def run_analyses(extractions: dict, grounding: dict, verifications: dict, focus: str) -> dict:
model = MODELS['analyst']
system = ('You are a senior investment analyst. Reason carefully across ALL provided evidence. '
'When the verification layer flags a claim UNVERIFIED or CONTRADICTED, say so explicitly. '
'Never invent numbers.')
parts = [f'[{k.upper()}]\n{v}' for k, v in extractions.items()]
if verifications.get('verdicts'):
lines = [
f" - [{v.get('status','?').upper()}] {v.get('text','')}"
+ (f' (evidence: “{v.get("evidence","")[:160]}”)' if v.get('evidence') else '')
for v in verifications['verdicts']
]
parts.append('[VERIFICATION]\n' + '\n'.join(lines))
if grounding:
blocks = []
for query, results in grounding.items():
rows = '\n'.join(
f" [{i+1}] {r['title']}\n {r['url']}\n {r['snippet'][:300]}"
for i, r in enumerate(results)
)
blocks.append(f'QUERY: {query}\n{rows}')
parts.append('[WEB EVIDENCE]\n' + '\n\n'.join(blocks))
context = '\n\n'.join(parts)
async def one(name: str, prompt: str):
user = f'\n{context}\n\n\nInvestor focus: {focus}\n\n{prompt}'
result = await chat(model, system, user)
print(f' ✓ {name}')
return name, result
print(f'[Analyze] {len(ANALYSIS_TASKS)} tasks in parallel (model: {model})')
return dict(await asyncio.gather(*[one(n, p) for n, p in ANALYSIS_TASKS]))
analyses = await run_analyses(extractions, grounding, verifications, FOCUS)
for name, text in analyses.items():
display(Markdown(f'### {name}\n\n{text}'))
```
## 10 · Stage 5 — Synthesis with citation & confidence invariants
One call to the best model. The prompt enforces three invariants the reader can verify:
1. **Citation** — every external fact gets an inline `[n]` linked to a numbered `Sources` list.
2. **Confidence tagging** — every company claim carries `[VERIFIED]`, `[PARTIAL]`, `[UNVERIFIED]`, or `[CONTRADICTED]`, taken from the verifier's output.
3. **No invented numbers** — every metric must come from the extractions or verification.
Invariants like these are what turn a fluent LLM memo into one a real investor can rely on.
```python theme={null}
async def run_synthesis(extractions: dict, grounding: dict, verifications: dict,
analyses: dict, focus: str) -> dict:
model = MODELS['synthesizer']
print(f'[Synthesize] Writing the memo (model: {model})')
sources, registry = [], []
for query, results in grounding.items():
for r in results:
sid = len(sources) + 1
sources.append(f"[{sid}] {r.get('title','')} — {r.get('url','')}\n query: {query}")
registry.append({'id': sid, 'title': r.get('title', ''), 'url': r.get('url', '')})
sections = ['=== EXTRACTIONS ===']
sections += [f'[{k.upper()}]\n{v}' for k, v in extractions.items()]
if verifications.get('verdicts'):
sections += [
'=== SOURCE VERIFICATION ===',
*[f"[{v.get('status','?').upper()}] {v.get('kind','')}: {v.get('text','')}" for v in verifications['verdicts']],
]
if sources:
sections += ['=== NUMBERED WEB SOURCES — cite as [n] ===', *sources]
sections += ['=== ANALYSES ===']
sections += [f'[{k.upper()}]\n{v}' for k, v in analyses.items()]
system = (
'You are writing a due-diligence memo for a professional investor. Be direct, specific, '
'and decision-oriented. Plain prose; avoid bullet spam.\n\n'
'RULES — all are mandatory:\n'
' 1. Cite every external fact with an inline [n] matching the NUMBERED WEB SOURCES section.\n'
' 2. Tag every non-trivial quantitative/factual claim with its confidence: [VERIFIED], '
'[PARTIAL], [UNVERIFIED], or [CONTRADICTED].\n'
' 3. Never invent numbers. Every metric must appear in the extractions or verification.\n'
' 4. If verification contradicted any claim, flag it prominently in Risks.\n'
' 5. End with a `Sources` section listing every [n] you cited.'
)
user = (
f"\n{chr(10).join(sections)}\n\n\n"
f'Write a due-diligence memo focused on: {focus}\n\n'
'Structure: Executive Summary · Company & Market · Team · Traction & Financials · Competitive '
'Landscape & Moat · Risks & Open Questions · Recommendation · Sources.'
)
report = await chat(model, system, user)
return {'report': report, 'sources': registry}
synthesis = await run_synthesis(extractions, grounding, verifications, analyses, FOCUS)
display(Markdown(synthesis['report']))
```
## 11 · Stage 6 — Multimodal output (TTS + image)
Two more primitives and a nice orchestration pattern.
* **`synthesize_speech`** — `POST /v1/audio/speech` returns WAV bytes.
* **`generate_image`** — `POST /v1/images/generations` returns a base64 PNG.
* **`synthesize_podcast`** — a mini-pipeline: ask the synthesizer to split the memo into short spoken chunks, TTS each chunk in parallel, stitch them into one WAV with silence gaps. This is the "plan → fan-out → stitch" pattern you can use for any long-form generation where latency matters.
The last cell runs it — skip if you don't want to burn credits on audio.
```python theme={null}
async def synthesize_speech(text: str, output_path: str) -> None:
client = AsyncOpenAI(api_key=API_KEY, base_url=BASE_URL + '/v1')
response = await client.audio.speech.create(
model=MODELS['tts'],
voice='Vivian',
input=text,
response_format='wav',
)
Path(output_path).write_bytes(response.content)
async def generate_image(prompt: str, output_path: str) -> None:
client = AsyncOpenAI(api_key=API_KEY, base_url=BASE_URL + '/v1')
response = await client.images.generate(
model=MODELS['image'],
prompt=prompt,
size='1024x1024',
n=1,
)
Path(output_path).write_bytes(base64.b64decode(response.data[0].b64_json))
def stitch_wav(paths: list, output_path: str, silence_ms: int = 350) -> None:
with wave.open(paths[0], 'rb') as probe:
params, framerate, sampwidth, nchannels = (
probe.getparams(), probe.getframerate(), probe.getsampwidth(), probe.getnchannels()
)
silence = b'\x00' * (int(framerate * silence_ms / 1000) * sampwidth * nchannels)
with wave.open(output_path, 'wb') as out:
out.setparams(params)
for i, p in enumerate(paths):
with wave.open(p, 'rb') as src:
out.writeframes(src.readframes(src.getnframes()))
if i < len(paths) - 1:
out.writeframes(silence)
async def synthesize_podcast(report: str, output_path: str) -> None:
print('[Podcast] Writing script...')
raw = await chat(
MODELS['synthesizer'],
'You are a podcast scriptwriter. Write punchy, natural spoken-word content.',
(
'Turn this report into 7–10 spoken chunks (~25 words each). '
'Return ONLY a JSON array: [{"text": "..."}]. No fences.\n\n'
f'Report:\n{report}'
),
)
try:
chunks = parse_json_response(raw)
except Exception:
chunks = [{'text': raw}]
print(f' ✓ {len(chunks)} chunks planned')
out_dir = Path(output_path).parent
async def tts_chunk(i, chunk):
tmp = str(out_dir / f'_chunk_{i:03d}.wav')
await synthesize_speech(chunk['text'], tmp)
return i, tmp
pairs = await asyncio.gather(*[tts_chunk(i, c) for i, c in enumerate(chunks)])
tmp_paths = [p for _, p in sorted(pairs)]
print('[Podcast] Stitching...')
stitch_wav(tmp_paths, output_path)
for p in tmp_paths:
Path(p).unlink(missing_ok=True)
print(f' ✓ {output_path}')
```
```python theme={null}
# Uncomment to actually run the output layer — generates demo_podcast.wav and demo_cover.png.
# from IPython.display import Audio, Image
#
# await synthesize_podcast(synthesis['report'], 'demo_podcast.wav')
# display(Audio('demo_podcast.wav'))
#
# img_prompt = await chat(
# MODELS['synthesizer'],
# 'You write one-sentence prompts for AI image generation.',
# f"One sentence describing a visual metaphor for this memo:\n\n{synthesis['report'][:1500]}",
# )
# await generate_image(img_prompt, 'demo_cover.png')
# display(Image('demo_cover.png'))
```
## What you've built
A full document → cited memo pipeline, in roughly 250 lines of Python, sitting entirely on top of Sciforium primitives:
* One endpoint (`/v1/chat/completions`) handled text extraction, verification, analysis, and synthesis.
* The same endpoint with a `file` content part did multimodal grounding against the original PDF.
* A separate attachments endpoint gave you high-fidelity text once, cached for every downstream text-only call.
* TTS and image endpoints rounded it out into a multimodal deliverable.
### Patterns worth keeping
* **Stack models by cost.** Cheap → mid → best, matched to call volume. Don't use your biggest model for 40 parallel extractions.
* **Fan out, then bound.** `asyncio.gather` for independent work; a `Semaphore` in front of any external API that can rate-limit you.
* **Batch big prompts.** Prefer five parallel 8-item prompts over one 40-item prompt — better throughput, better UX, better error isolation.
* **Cache the parse.** Run the attachments API once, reuse the text. Attach the file only for the passes that truly need native bytes.
* **Enforce invariants in the synthesis prompt.** Citations, confidence tags, no invented numbers — these are what separate a demo from a trustworthy artefact.
### Where to go next
* Swap `MODELS` values to try different verifiers, analysts, or synthesizers.
* Extend `EXTRACTION_TASKS` / `ANALYSIS_TASKS` — new lenses cost roughly nothing since they fan out in parallel.
* Add retries with backoff around `chat()` (see the Exa helper for the pattern) for production robustness.
* Batch mode: process many PDFs and bucket/rank them — same building blocks, wrapped in an outer `asyncio.Semaphore`.
# Attachment Parser API
Source: https://docs.sciforium.com/attachment-processor
## Overview
The parse endpoint extracts text content from files (PDFs, images, DOCX, text files). Files are sent as base64-encoded payloads and processed in batch.
## Supported file types
| MIME type | Description |
| ------------------------------------------------------------------------- | ----------------------- |
| `application/pdf` | PDF documents |
| `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | DOCX files |
| `application/msword` | Legacy DOC files |
| `application/json` | JSON files |
| `text/*` | Any plain text file |
| `image/*` | Images (OCR extraction) |
## Limits
* Max file size: **20 MB** per file
* Max files per request: **20**
* Max pages (PDF): **50** (configurable via `options.max_pages`)
* Request timeout: **110 seconds**
***
## 1. The Request
**Method:** `POST`
**Endpoint:** `https://api.sciforium.com/api/attachments/parse`
**Content-Type:** `application/json`
### Request fields
| Field | Type | Required | Description |
| -------------------- | ------- | -------- | -------------------------------------- |
| `files` | array | Yes | `1..20` file objects |
| `files[].url` | string | Yes | Data URI: `data:;base64,` |
| `files[].filename` | string | Yes | File name (max 255 chars) |
| `files[].media_type` | string | No | MIME type hint |
| `options.max_pages` | integer | No | `1..50`, default `50` |
### Example CURL Request (PDF)
```bash theme={null}
curl -X POST "https://api.sciforium.com/api/attachments/parse" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"files": [
{
"url": "data:application/pdf;base64,JVBERi0xLjQKMSAwIG9...",
"filename": "invoice.pdf",
"media_type": "application/pdf"
}
],
"options": {
"max_pages": 10
}
}'
```
## Example response — `POST /api/attachments/parse`
`200 OK` — `Content-Type: application/json`
### Success (one file completed)
```json theme={null}
{
"id": "parse_7f3c2a1b-9d8e-4f6c-a5b4-3210fedcba98",
"object": "parse.batch_result",
"results": [
{
"filename": "invoice.pdf",
"status": "completed",
"content": {
"text": "Invoice #10248\nDate: 2026-04-01\nTotal: $128.50"
}
}
],
"metadata": {
"total_files": 1,
"completed": 1,
"failed": 0,
"total_processing_time_ms": 342
}
}
```
### Partial Success (eg. Page Limit)
```json theme={null}
{
"id": "parse_aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"object": "parse.batch_result",
"results": [
{
"filename": "long-report.pdf",
"status": "partial",
"content": {
"text": "…extracted text for the first N pages…"
}
}
],
"metadata": {
"total_files": 1,
"completed": 1,
"failed": 0,
"total_processing_time_ms": 8900
}
}
```
### Per-file Error
```json theme={null}
{
"id": "parse_bbbbbbbb-cccc-dddd-eeee-ffffffffffff",
"object": "parse.batch_result",
"results": [
{
"filename": "corrupt.pdf",
"status": "error",
"error": {
"code": "PROCESSING_FAILED",
"message": "Could not read PDF structure"
}
}
],
"metadata": {
"total_files": 1,
"completed": 0,
"failed": 1,
"total_processing_time_ms": 120
}
}
```
### Mixed batch (one OK, one Failed)
```text theme={null}
{
"id": "parse_cccccccc-dddd-eeee-ffff-000000000000",
"object": "parse.batch_result",
"results": [
{
"filename": "notes.txt",
"status": "completed",
"content": {
"text": "Meeting notes\n- Action items…"
}
},
{
"filename": "unknown.xyz",
"status": "error",
"error": {
"code": "UNSUPPORTED_FORMAT",
"message": "Unsupported file format"
}
}
],
"metadata": {
"total_files": 2,
"completed": 1,
"failed": 1,
"total_processing_time_ms": 210
}
}
```
# Audio Transcription
Source: https://docs.sciforium.com/audio
The `/v1/audio/transcriptions` API turns uploaded audio into text. For typical files it behaves as a **single request/response** (upload the file, get the transcript back).
## 1. The Request
**Method:** `POST`\
**Endpoint:** `https://api.sciforium.com/v1/audio/transcriptions`\
**Content-Type:** `multipart/form-data`
### Request body parameters
| Parameter | Type | Required | Description |
| --------------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------- |
| `file` | file | Yes | Audio to transcribe. Common formats: mp3, mp4, m4a, wav, webm. Max **25 MB** (enforced server-side). |
| `model` | string | Yes | Model ID your deployment supports (see your model list / console). |
| `language` | string | No | Language hint (ISO-639-1), e.g. `en`, `fr`, `es`. |
| `prompt` | string | No | Optional hint for style, vocabulary, or domain terms. |
| `response_format` | string | No | Output shape, e.g. `json`, `text`, `verbose_json`, `srt`, `vtt` (depends on model). |
| `timestamp_granularities[]` | array | No | For some models / formats (e.g. word or segment granularity in verbose output). |
### Example request (CURL)
```bash theme={null}
curl -X POST "https://api.sciforium.com/v1/audio/transcriptions" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $TOKEN" \
-F "file=@interview_audio.mp3" \
-F "model=YOUR_MODEL_ID" \
-F "response_format=json"
```
## 2. The response
Response shape depends on `response_format` and model support.
### Common response formats
| response\_format | Typical response |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `wav` | Standard uncompressed WAV audio; best compatibility with tools/players. |
| `pcm` | Raw 16-bit PCM audio bytes (typically 24kHz mono); best for low-latency streaming pipelines. |
| `opus` | Compressed Opus audio; much smaller files with good speech quality. |
## Example response
### `response_format=wav`
```json theme={null}
{
"format": "wav",
"content_type": "audio/wav",
"audio_base64": "UklGRiQAAABXQVZFZm10IBAAAAABAAEA..."
}
```
### `response_format=pcm`
```json theme={null}
{
"format": "pcm",
"content_type": "audio/octet-stream",
"audio_base64": "kP8A/wD+AP0A/AD7APoA+QD4..."
}
```
### `response_format=opus`
```json theme={null}
{
"format": "opus",
"content_type": "audio/ogg",
"audio_base64": "T2dnUwACAAAAAAAAAADY8kQeAAAAA..."
}
```
# Audio Speech
Source: https://docs.sciforium.com/audio-1
The v1/audio/speech endpoint is designed to convert written text into lifelike spoken audio.
## 1. The Request
**Method:** POST
**Endpoint:** `https://api.openai.com/v1/audio/speech`
**Content-Type:** application/json
### Request Body Parameters
| **Parameter** | **Type** | **Required** | **Description** |
| :------------------- | :------- | :----------- | :-------------------------------------------------------------------------------------------------------------- |
| **model** | string | **Yes** | The model ID. Options: gpt-4o-mini-tts (steerable), tts-1 (low latency), or tts-1-hd (high quality). |
| **input** | string | **Yes** | The text to be turned into audio. (Max 4,096 characters). |
| **voice** | string | **Yes** | The voice ID to use. Options include: alloy, echo, fable, onyx, nova, shimmer, coral, ash, sage, marine, cedar. |
| **response\_format** | string | No | Output format. Options: mp3 (default), opus, aac, flac, wav, or pcm. |
| **speed** | number | No | The speed of the generated audio from 0.25 to 4.0. (Default is 1.0). |
**Example Request (cURL):**
```bash theme={null}
curl https://api.openai.com/v1/audio/speech \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
"input": "I have a very important secret to tell you... but you must promise not to tell anyone.",
"voice": "shimmer",
"instructions": "Whisper in a mysterious and slightly urgent tone.",
"speed": 0.9
}' \
--output secret_message.mp3
```
***
## 2. The Response
For `POST /v1/audio/speech`, the response is **binary audio bytes**, not JSON.
### Success response
* HTTP status: `200`
* Body: non-empty binary data
* `Content-Type`: **not** `application/json` (typically `audio/wav` when `response_format=wav`)
### Example (HTTP-style)
```http theme={null}
HTTP/1.1 200 OK
Content-Type: audio/wav
Content-Length: 124830
[binary audio bytes...]
```
### Example CURL to save response
```text theme={null}
curl -X POST "https://api.sciforium.com/v1/audio/speech" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
"input": "The quick brown fox jumps over the lazy dog.",
"voice": "Vivian",
"response_format": "wav"
}' \
--output speech.wav
```
# Authentication
Source: https://docs.sciforium.com/authentication
Include your API key in the `Authorization` header of every request:
```text theme={null}
Authorization: Bearer YOUR_API_KEY
```
Requests without a valid key return `401 Unauthorized`. Keys are organization-scoped.
# Base URL & Endpoints
Source: https://docs.sciforium.com/base-url-and-endpoints
## Base URL
```text theme={null}
https://api.sciforium.com/v1
```
## Endpoints
| Endpoint | Method | Description |
| ----------------------- | ------ | ---------------------------- |
| `/models` | GET | List available models |
| `/chat/completions` | POST | Generate a chat completion |
| `/audio/transcriptions` | POST | Transcribe audio to text |
| `/audio/speech` | POST | Text-to-speech |
| `/images/generations` | POST | Generate images |
| `/images/edits` | POST | Edit Images |
| `/videos` | POST | Create a video job |
| `/videos/{id}/content` | GET | Download video content (MP4) |
| `/videos/{id}` | GET | Get video job status |
| | | |
# Billing
Source: https://docs.sciforium.com/billing
Sciforium uses a prepaid credits system. Credits are deducted as requests are processed, based on tokens consumed and GPU seconds used. Your current balance, total purchased, and total consumed are shown on the Billing page. Credits never expire once purchased.
## Buying Credits
1. Click **Billing** in the top navigation bar.
2. In the Buy Credits panel, drag the slider or type a custom amount. Minimum purchase is \$5.
3. Click **+ Buy Credits** and complete the Stripe checkout.
Credits are applied to your balance immediately on successful payment. Payment details are handled entirely by Stripe.
## Purchase History
The Purchase History table shows all past transactions with amount, date, and description. Click the download icon on any row to save a receipt.
## Service Suspension Policy
When your credit balance reaches \$0.00, API requests are automatically blocked and return a 402 error. Your account, keys, and data are not affected — service resumes as soon as you add credits.
## Quotas & Rate Limits
Sciforium enforces rate limits to ensure platform stability.
**Rate limits (fixed for all users):**
* **Requests per minute (RPM):** 60 requests per minute per API key
If you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header. Implement exponential backoff in your integration.
**Quota limits (Admin-configurable per key):**
* **Daily spend limit:** Maximum credit spend per key per day
* **Monthly spend limit:** Maximum credit spend per key per month
When a limit is reached, that key returns 429 with a message indicating the quota type and reset time.
## Setting Budget Controls
1. Open the settings for the key you want to limit from the API Keys dashboard.
2. Set a daily spend limit, monthly spend limit, or both.
3. Save. The limits take effect immediately.
Quota status is visible in the dashboard with color-coded indicators: normal (0–79%), warning (80–94%), critical (95–99%), and exceeded (100%+).
# Chat Completions
Source: https://docs.sciforium.com/chat-completion-request
**Method:** `POST`\
**Endpoint:** `https://api.sciforium.com/v1/chat/completions`
### Overview
The `/v1/chat/completions` endpoint generates conversational responses from a sequence of messages and generation parameters.
### Request Body Parameters
The API expects a JSON body. `model` and `messages` are required.
| Parameter | Type | Required | Description |
| ----------------------- | ------------------- | -------- | ------------------------------------------------------------------------------------------ |
| `model` | string | Yes | The model ID to use (e.g., `deepseek-r1-distill-llama-8b`, `qwen-2.5-7b`, `gpt-oss-120b`). |
| `messages` | array | Yes | Array of message objects with `role` and `content`. |
| `temperature` | number | No | Sampling temperature (`0` to `2`). |
| `top_p` | number | No | Nucleus sampling probability (`0` to `1`). |
| `max_tokens` | number | No | Maximum output tokens. |
| `max_completion_tokens` | number | No | Alternative max-output-token field (compatibility). |
| `presence_penalty` | number | No | Presence penalty (`-2` to `2`). |
| `frequency_penalty` | number | No | Frequency penalty (`-2` to `2`). |
| `seed` | integer | No | Deterministic sampling seed. |
| `stop` | string or string\[] | No | Stop sequence(s). |
| `n` | integer | No | Number of completions to generate. |
| `stream` | boolean | No | Stream tokens as SSE when `true`. |
| `stream_options` | object | No | Streaming options, e.g. `{ "include_usage": true }`. |
### Example response
The API returns a JSON object containing the completion and usage metadata.
* `id`: Unique identifier for the completion.
* `object`: Object type (`chat.completion`).
* `model`: Model used for generation.
* `choices`: Array of completion objects, each with:
* `index`: Choice index.
* `message`: Assistant message with `role` and `content`.
* `finish_reason`: Reason for completion (`stop`, `length`, etc.).
* `usage`: Token usage statistics including `prompt_tokens`, `completion_tokens`, `total_tokens`, and `gpu_seconds`.
```json theme={null}
{
"id": "chatcmpl-abc123",
"object": "chat.completion",
"model": "deepseek-r1-distill-llama-8b",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "The capital of France is Paris." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 24,
"completion_tokens": 9,
"total_tokens": 33,
"gpu_seconds": 0.041
}
}
```
# Creating an API Key
Source: https://docs.sciforium.com/creating-an-api-key
Open the left sidebar and select **API Keys**.
Click **+ Create API Key** in the top right.
Provide a name that identifies the key's purpose (e.g., `production-backend`, `dev-testing`).
Click **Create**. Copy the key immediately—it is shown only once. Store it in a secrets manager or environment vari
# Data and Filtering
Source: https://docs.sciforium.com/data-and-filtering
## Data Overview
The Analytics dashboard provides a real-time view of your organization's inference activity. Access it via Analytics in the left sidebar. Usage is shown across all members of your organization.
Three tabs are available:
| Tab | Description |
| ------- | ------------------------------------------------------------------------------------------ |
| Credits | Time-series chart of credit consumption by model. Toggle between Area and Bar chart views. |
| Tokens | Token-level breakdown of input vs. output tokens over time. |
| Media | Usage for image generation, video generation, and audio models. |
## Filtering
| Filter | Description |
| ------------ | ------------------------------------------------------------------------ |
| Model filter | Use the Model dropdown to isolate usage for a specific model. |
| Date range | Click the date field to set a custom range. Default is the past 30 days. |
# Error Codes
Source: https://docs.sciforium.com/error-codes
| Code | Meaning | Resolution |
| ---- | --------------------------- | -------------------------------------------------------------------------------------- |
| 400 | Bad Request | Check your request body for missing or malformed fields. |
| 401 | Unauthorized | API key missing or invalid. Verify in your API Keys dashboard. |
| 402 | Payment Required | Insufficient credits. Add credits via the Billing page. |
| 403 | Forbidden | Your account lacks permission for this action. |
| 429 | Rate Limit / Quota Exceeded | Exceeded requests per minute or spend quota. Wait and retry, or review quota settings. |
| 500 | Internal Server Error | Unexpected error on Sciforium's end. Retry the request. |
| 503 | Service Unavailable | Model temporarily unavailable. Check status and retry. |
# Introduction
Source: https://docs.sciforium.com/getting-started
Sciforium is a serverless AI inference platform giving developers and teams instant access to a broad library of open-source and frontier models - no infrastructure to manage. Point your existing code at Sciforium's API, pick a model, and start running inference in minutes.
Run text, vision, image generation, and speech-to-text inference via a simple REST API
Explore models interactively in the Playground before writing a line of code
Monitor usage and costs in real time through the Analytics dashboard
Manage team access, API keys, and spend controls from a single console
Sciforium is built for developers and teams who want the flexibility of open-source models with the reliability of a managed service.
# Image Generations
Source: https://docs.sciforium.com/image-completion
**Method:** `POST`\
**Endpoint:** `https://api.sciforium.com/v1/images/generations`
### Overview
The `/v1/images/generations` endpoint creates images from a text prompt and generation parameters.
### Request Body Parameters
The API expects a JSON body. `model` and `prompt` are required.
| Parameter | Type | Required | Description |
| ----------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `model` | string | Yes | The model ID to use. |
| `prompt` | string | Yes | A text description of the desired image. |
| `n` | integer | No | Number of images to generate (commonly `1`). |
| `size` | string | No | Output dimensions. Typical values include `1024x1024`, `1536x1024`, `1024x1536`, or `auto` (model-dependent). |
| `quality` | string | No | Rendering quality (model-dependent), e.g. `low`, `medium`, `high`. |
| `response_format` | string | No | Response payload format: `url` or `b64_json`. |
| `background` | string | No | Background mode (model-dependent), e.g. `transparent`, `opaque`, `auto`. |
| `style` | string | No | Not currently supported. |
### Example response
The API returns a JSON object containing a timestamp and a `data` array.
* `created`: Unix timestamp when the request was processed.
* `data`: Array of image objects, each with:
* `url` or `b64_json`: image output
* `revised_prompt`: prompt rewritten by the model
```json theme={null}
{
"created": 1775612596,
"data": [
{
"url": "https://oaidalleapiprodscus.blob.core.windows.net/...",
"revised_prompt": "A sprawling, futuristic metropolitan skyline constructed entirely over shimmering turquoise ocean waters. Skyscrapers are connected by glass bridges, with neon lights reflecting off the waves. The scene is captured in a cinematic style with high contrast and sharp 8k detail."
}
]
}
```
# Image Edits
Source: https://docs.sciforium.com/image-edits
**Method:** `POST`\
**Endpoint:** `https://api.sciforium.com/v1/images/edits`\
**Content-Type:** `multipart/form-data`
### Overview
`/v1/images/edits` edits one or more input images using a text prompt.
### Request Body Parameters
| Parameter | Type | Required | Description |
| --------------------- | --------------- | -------- | ------------------------------------------------------------------ |
| `image` | file or file\[] | Yes | Source image(s). PNG/JPEG/WebP. Max file size: 50 MB each. |
| `prompt` | string | Yes | Description of the final desired image (1 to 32,000 chars). |
| `model` | string | Yes | Image model ID. |
| `mask` | file | No | Optional mask file for targeted edits. |
| `input_fidelity` | string | No | `low` or `high`. Default: `low`. |
| `n` | integer | No | Number of outputs, `1` to `10`. Default: `1`. |
| `size` | string | No | `1024x1024`, `1536x1024`, `1024x1536`, or `auto`. Default: `auto`. |
| `quality` | string | No | `low`, `medium`, `high`, or `auto`. Default: `auto`. |
| `background` | string | No | `transparent`, `opaque`, or `auto`. Default: `auto`. |
| `output_format` | string | No | `png`, `jpeg`, or `webp`. Default: `png`. |
| `output_compression` | integer | No | `0` to `100`. Default: `100`. |
| `moderation` | string | No | `auto` or `low`. Default: `auto`. |
| `num_inference_steps` | integer | No | Optional step count (`1` to `50`). |
| `partial_images` | integer | No | Streaming partials (`0` to `3`). Default: `0`. |
| `response_format` | string | No | `b64_json` or `url`. |
| `user` | string | No | Optional user identifier. |
| `stream` | boolean | No | Stream events when enabled. |
> Pro tip: describe the **full final scene**, not only the delta.
### Example Response
```json theme={null}
{
"created": 1775615000,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA...",
"revised_prompt": "The original photograph of a living room is modified to include a modern black leather sofa where the wooden chair was, while maintaining the identical lighting and wall color."
}
],
"usage": {
"input_tokens": 1200,
"output_tokens": 0,
"total_tokens": 1200
},
"filtered_count": 0
}
```
# Managing Keys
Source: https://docs.sciforium.com/managing-keys
Your API Keys dashboard shows all active keys with names and creation dates.
### Revoke a key
Click the options menu next to the key and select **Revoke**. The key is immediately invalidated — requests using it return 401.
### Rotate a key
1. Create a new key
2. Update your integration
3. Verify it works
4. Revoke the old key
### Quota settings
Admins can configure daily and monthly spend limits per key. See Billing & Budget Control below.
# Model Library
Source: https://docs.sciforium.com/model-library
| Model | Description | Type | State | Creator | Model ID | Weight dtype | Activation dtype | KV Cache dtype | Fine-Tuning | Serverless | Context Length | Embeddings | Input Modality | Output Modality |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------- | -------------------- | ----------------------------------- | ------------ | ---------------- | -------------- | ------------- | ---------- | -------------- | ------------- | -------------- | --------------- |
| MiniMax M2.5 | MiniMax M2.5 is a frontier mixture-of-experts model with 230B total / 10B active params, trained via large-scale reinforcement learning. Scores 80.2% on SWE-Bench Verified and 76.3% on BrowseComp. | LLM | Ready | MiniMax | MiniMaxAI/MiniMax-M2.5 | FP8 | FP16 | FP16 | Contact Sales | Supported | 197K | Not Supported | Text | Text |
| Kimi K2.5 | Kimi K2.5 is a native multimodal MoE with 1T total / 32B active params, supporting instant and thinking modes, agent swarm coordination of up to 100 sub-agents. | LLM | Ready | Moonshot AI | moonshotai/Kimi-K2.5 | BF16 | BF16 | BF16 | Contact Sales | Supported | 262K | Not Supported | Text / Image | Text |
| GLM 5 | GLM-5 is a MoE model from Z.ai with 744B total / 40B active params, trained on 28.5T tokens. Scores 73.3% on SWE-bench Verified. | LLM | Contact Sales | Z.ai (Zhipu) | GLM-5 | BF16 | BF16 | BF16 | Contact Sales | Supported | 203K | Not Supported | Text | Text |
| DeepSeek V3.2 | DeepSeek V3.2 is a MoE with 671B total / 37B active params. Introduces 'thinking with tools' capability; achieves gold-medal results at 2025 IMO and IOI. | LLM | Ready | DeepSeek | deepseek-ai/DeepSeek-V3.2 | FP8 | FP16 | FP16 | Contact Sales | Supported | 164K | Not Supported | Text | Text |
| gpt-oss-120b | Open-weight MoE from OpenAI with 117B total / 5.1B active params per forward pass, runs on a single H100 or AMD MI300X GPU using MXFP4 quantization. | LLM | Ready | OpenAI | openai/gpt-oss-120b | BF16 | BF16 | BF16 | Contact Sales | Supported | 131K | Not Supported | Text | Text |
| gpt-oss-20b | Open-weight MoE from OpenAI with 21B total / 3.6B active params, designed for low-latency inference on consumer or single-GPU hardware. | LLM | Contact Sales | OpenAI | gpt-oss-20b | BF16 | BF16 | BF16 | Contact Sales | Supported | 131K | Not Supported | Text | Text |
| Qwen3 Instruct | MoE LLM with 235B total / 22B active params, optimized for instruction following across 100+ languages. 256K token context window. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-235B-A22B-Instruct-2507 | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text | Text |
| Qwen3 Thinking | MoE reasoning model with 235B total / 22B active params, designed for deep logic, math, science, and complex multi-step coding tasks. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-235B-A22B-Instruct-2507 | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text | Text |
| Qwen3 Coder | MoE model with 480B total / 35B active params, purpose-built for agentic coding. Native 256K context window, scalable to 1M tokens. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-Coder-480B-A35B-Instruct | BF16 | BF16 | BF16 | Contact Sales | Supported | 262K | Not Supported | Text | Text |
| Qwen3.5 | Multimodal MoE with 397B total / 17B active params and hybrid Gated Delta Network architecture enabling 1M token context window. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3.5-397B-A17B | BF16 | BF16 | BF16 | Contact Sales | Supported | 1M | Not Supported | Text / Image | Text |
| Qwen3 VL Instruct | Vision-language MoE with 30B total / 3B active params, supporting megapixel-level inputs, multilingual OCR, visual grounding, and GUI automation. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-VL-235B-A22B-Instruct | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text / Image | Text |
| Qwen3 ASR | Advanced speech recognition with 1.7B params, supporting 52 languages, streaming and offline inference. Built on Qwen3-Omni architecture. | Audio | Ready | Alibaba / Qwen | Qwen/Qwen3-ASR-1.7B | BF16 | BF16 | BF16 | Contact Sales | Supported | N/A | Not Supported | Audio | Text |
| Qwen-Image | Open-weight diffusion transformer from Alibaba with strong text rendering accuracy and multilingual typography. | Image | Ready | Alibaba / Qwen | Qwen/Qwen-Image | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Qwen-Image-Edit | Dedicated image editing variant of Qwen-Image series, supporting style transfer, object insertion, and detail enhancement. | Image | Ready | Alibaba / Qwen | Qwen/Qwen-Image-Edit-2511 | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text / Image | Image |
| Flux2 4B | Production-grade image generation from Black Forest Labs with 4B parameter flow transformer, using rectified flow matching for efficient inference. | Image | Ready | Black Forest Labs | black-forest-labs/FLUX.2-klein-4B | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Stable Diffusion 3.5 | Diffusion transformer from Stability AI with the most mature open-source tooling ecosystem (ComfyUI, Automatic1111, Forge). | Image | Contact Sales | Stability AI | stable-diffusion-3.5-large | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Hunyuan Image | Large-scale multimodal autoregressive image generation from Tencent with 80B total params across 64 MoE experts, trained on 5B image-text pairs. | Image | Ready | Tencent | tencent/HunyuanImage-3.0-Instruct | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Z-Image | Open-weight diffusion transformer (Apache 2.0) for ultra-fast inference with accurate bilingual text rendering in English and Chinese. | Image | Contact Sales | Tongyi-MAI / Alibaba | Z-Image-Turbo | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Wan2.2-I2V | Open-source video diffusion model from Alibaba for image-to-video generation with strong motion consistency and scene coherence. | Video | Ready | Alibaba / Wan | Wan-AI/Wan2.2-T2V-A14B-Diffusers | FP16 | FP16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text / Image | Video |
| Wan2.2-T2V | Open-source video diffusion model from Alibaba for text-to-video generation with improved motion dynamics over Wan2.1. | Video | Ready | Alibaba / Wan | Wan-AI/Wan2.2-I2V-A14B-Diffusers | FP16 | FP16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Video |
| Model | Description | Type | State | Creator | Model ID | Weight dtype | Activation dtype | KV Cache dtype | Fine-Tuning | Serverless | Context Length | Embeddings | Input Modality | Output Modality |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ------------- | -------------- | ----------------------------------- | ------------ | ---------------- | -------------- | ------------- | ------------- | -------------- | ------------- | -------------- | --------------- |
| MiniMax M2.5 | MiniMax M2.5 is a frontier mixture-of-experts model with 230B total / 10B active params, trained via large-scale reinforcement learning. Scores 80.2% on SWE-Bench Verified and 76.3% on BrowseComp. | LLM | Ready | MiniMax | MiniMaxAI/MiniMax-M2.5 | FP8 | FP16 | FP16 | Contact Sales | Supported | 197K | Not Supported | Text | Text |
| Kimi K2.5 | Kimi K2.5 is a native multimodal MoE with 1T total / 32B active params, supporting instant and thinking modes, agent swarm coordination of up to 100 sub-agents. | LLM | Ready | Moonshot AI | moonshotai/Kimi-K2.5 | BF16 | BF16 | BF16 | Contact Sales | Supported | 262K | Not Supported | Text / Image | Text |
| GLM 5 | GLM-5 is a MoE model from Z.ai with 744B total / 40B active params, trained on 28.5T tokens. Scores 73.3% on SWE-bench Verified. | LLM | Contact Sales | Z.ai (Zhipu) | GLM-5 | BF16 | BF16 | BF16 | Contact Sales | Not Supported | 203K | Not Supported | Text | Text |
| DeepSeek V3.2 | DeepSeek V3.2 is a MoE with 671B total / 37B active params. Introduces 'thinking with tools' capability; achieves gold-medal results at 2025 IMO and IOI. | LLM | Ready | DeepSeek | deepseek-ai/DeepSeek-V3.2 | FP8 | FP16 | FP16 | Contact Sales | Supported | 164K | Not Supported | Text | Text |
| gpt-oss-120b | Open-weight MoE from OpenAI with 117B total / 5.1B active params per forward pass, runs on a single H100 or AMD MI300X GPU using MXFP4 quantization. | LLM | Ready | OpenAI | openai/gpt-oss-120b | BF16 | BF16 | BF16 | Contact Sales | Supported | 131K | Not Supported | Text | Text |
| gpt-oss-20b | Open-weight MoE from OpenAI with 21B total / 3.6B active params, designed for low-latency inference on consumer or single-GPU hardware. | LLM | Contact Sales | OpenAI | gpt-oss-20b | BF16 | BF16 | BF16 | Contact Sales | Not Supported | 131K | Not Supported | Text | Text |
| Qwen3 Instruct | MoE LLM with 235B total / 22B active params, optimized for instruction following across 100+ languages. 256K token context window. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-235B-A22B-Instruct-2507 | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text | Text |
| Qwen3 Thinking | MoE reasoning model with 235B total / 22B active params, designed for deep logic, math, science, and complex multi-step coding tasks. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-235B-A22B-Instruct-2507 | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text | Text |
| Qwen3 Coder | MoE model with 480B total / 35B active params, purpose-built for agentic coding. Native 256K context window, scalable to 1M tokens. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-Coder-480B-A35B-Instruct | BF16 | BF16 | BF16 | Contact Sales | Supported | 262K | Not Supported | Text | Text |
| Qwen3.5 | Multimodal MoE with 397B total / 17B active params and hybrid Gated Delta Network architecture enabling 1M token context window. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3.5-397B-A17B | BF16 | BF16 | BF16 | Contact Sales | Supported | 1M | Not Supported | Text / Image | Text |
| Qwen3 VL Instruct | Vision-language MoE with 30B total / 3B active params, supporting megapixel-level inputs, multilingual OCR, visual grounding, and GUI automation. | LLM | Ready | Alibaba / Qwen | Qwen/Qwen3-VL-235B-A22B-Instruct | BF16 | BF16 | BF16 | Contact Sales | Supported | 256K | Not Supported | Text / Image | Text |
| Model | Description | Type | State | Creator | Model ID | Weight dtype | Activation dtype | KV Cache dtype | Fine-Tuning | Serverless | Context Length | Embeddings | Input Modality | Output Modality |
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- | ----- | -------------- | ------------------- | ------------ | ---------------- | -------------- | ------------- | ---------- | -------------- | ------------- | -------------- | --------------- |
| Qwen3 ASR | Advanced speech recognition with 1.7B params, supporting 52 languages, streaming and offline inference. Built on Qwen3-Omni architecture. | Audio | Ready | Alibaba / Qwen | Qwen/Qwen3-ASR-1.7B | BF16 | BF16 | BF16 | Contact Sales | Supported | N/A | Not Supported | Audio | Text |
| Model | Description | Type | State | Creator | Model ID | Weight dtype | Activation dtype | KV Cache dtype | Fine-Tuning | Serverless | Context Length | Embeddings | Input Modality | Output Modality |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | ------------- | -------------------- | --------------------------------- | ------------ | ---------------- | -------------- | ------------- | ---------- | -------------- | ------------- | -------------- | --------------- |
| Qwen-Image | Open-weight diffusion transformer from Alibaba with strong text rendering accuracy and multilingual typography. | Image | Ready | Alibaba / Qwen | Qwen/Qwen-Image | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Qwen-Image-Edit | Dedicated image editing variant of Qwen-Image series, supporting style transfer, object insertion, and detail enhancement. | Image | Ready | Alibaba / Qwen | Qwen/Qwen-Image-Edit-2511 | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text / Image | Image |
| Flux2 | Production-grade image generation from Black Forest Labs with 9B parameter flow transformer, using rectified flow matching for efficient inference. | Image | Ready | Black Forest Labs | black-forest-labs/FLUX.2-klein-4B | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Stable Diffusion 3.5 | Diffusion transformer from Stability AI with the most mature open-source tooling ecosystem (ComfyUI, Automatic1111, Forge). | Image | Contact Sales | Stability AI | stable-diffusion-3.5-large | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Hunyuan Image | Large-scale multimodal autoregressive image generation from Tencent with 80B total params across 64 MoE experts, trained on 5B image-text pairs. | Image | Ready | Tencent | tencent/HunyuanImage-3.0-Instruct | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Z-Image | Open-weight diffusion transformer (Apache 2.0) for ultra-fast inference with accurate bilingual text rendering in English and Chinese. | Image | Contact Sales | Tongyi-MAI / Alibaba | Z-Image-Turbo | BF16 | BF16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Image |
| Model | Description | Type | State | Creator | Model ID | Weight dtype | Activation dtype | KV Cache dtype | Fine-Tuning | Serverless | Context Length | Embeddings | Input Modality | Output Modality |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | ----- | ------------- | -------------------------------- | ------------ | ---------------- | -------------- | ------------- | ---------- | -------------- | ------------- | -------------- | --------------- |
| Wan2.2-I2V | Open-source video diffusion model from Alibaba for image-to-video generation with strong motion consistency and scene coherence. | Video | Ready | Alibaba / Wan | Wan-AI/Wan2.2-I2V-A14B-Diffusers | FP16 | FP16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text / Image | Video |
| Wan2.2-T2V | Open-source video diffusion model from Alibaba for text-to-video generation with improved motion dynamics over Wan2.1. | Video | Ready | Alibaba / Wan | Wan-AI/Wan2.2-T2V-A14B-Diffusers | FP16 | FP16 | N/A | Contact Sales | Supported | N/A | Not Supported | Text | Video |
# OpenAI Compatability
Source: https://docs.sciforium.com/open-ai-compatability
Sciforium's API is a drop-in replacement for the OpenAI Chat Completions API. Most OpenAI SDK methods, request shapes, and response formats work without further changes.
## Migration steps
To migrate from OpenAI to Sciforium:
1. **Change the base URL** from `https://api.openai.com/v1` to `https://api.sciforium.com/v1`
2. **Replace your API key** with a Sciforium API key
3. **Update the model field** to a Sciforium model ID
Most OpenAI SDK methods, request shapes, and response formats work without further changes.
# Overview
Source: https://docs.sciforium.com/overview
The Model Library is accessible from the left sidebar. Each model card shows a plain-language description, metadata, and pricing. Toggle between list and grid views using the icons in the top right.
Click **Model Detail** to see the full model page, or click **Select** from the Home screen to open that model directly in the Playground.
## Model Metadata Reference
| **Field** | **Description** |
| :------------- | :------------------------------------------------------------------------- |
| State | Availability status. Ready means the model is live and accepting requests. |
| Created on | Date the model was added to Sciforium. |
| Kind | Model type (e.g. Base Model). |
| Provider | The organization that trained the model. |
| Hugging Face | Base model identifier on Hugging Face. |
| Uncached Input | Cost per million input tokens without caching. |
| Cached Input | Cost per million input tokens with prompt caching active. |
| Output | Cost per million output tokens generated. |
| Context | Maximum token window (input + output) for a single request. |
# Using the Playground
Source: https://docs.sciforium.com/overview-1
# Overview
The Playground lets you interact with any model directly in the browser — no code required. It's the fastest way to evaluate a model, test a prompt, or explore parameters before integrating via API.
**To use the Playground:**
1. Click **Playground** in the left sidebar, or select a model from the Home screen.
2. The active model appears in the dropdown at the top of the left panel. Click it to switch models.
3. Adjust parameters in the left panel as needed.
4. Type your prompt in the input box on the right and press Enter or the arrow button.
5. Responses stream in real time. Click **New Chat** in the top right to start a fresh session.
Additional toolbar options let you share, reset, or download the current session.
# Configurations
## LLM
| Configuration | Description |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Temperature** | Controls randomness. Lower values (e.g. 0.2) produce focused, deterministic responses. Higher values (e.g. 1.0) increase creativity. Default: 0.7. |
| **Max. Tokens** | Maximum tokens the model generates in a single response. Default: 2048. |
| **Top P** | Nucleus sampling threshold. 1.0 disables nucleus sampling. Default: 1. |
| **Presence Penalty** | Penalizes tokens already in the output, encouraging new topics. Range: -2.0 to 2.0. |
| **Frequency Penalty** | Penalizes repeated tokens proportionally, reducing word-level repetition. Range: -2.0 to 2.0. |
| **Stop** | One or more strings that cause the model to stop generating. |
| **Seed** | Fixed seed for reproducible outputs across identical requests. |
| **Top Logprobs** | Returns log probabilities for the top N tokens at each position. Useful for debugging. |
| **N (Choices)** | Number of independent completion responses generated per request. |
## Image
| Configuration | Description |
| :-------------- | :----------------------------------------------------------------------------------------------------------------------------------- |
| Size | Dimensions of the generated image (e.g. 512×512, 1024×1024). Larger sizes produce more detail but increase generation time and cost. |
| Image (n) | Number of independent images generated per request. |
| Inference Steps | Number of denoising steps during generation. More steps generally improve quality and detail but increase latency. |
| Seed | Fixed seed for reproducible image outputs across identical requests. |
## Video
| Configuration | Description |
| :------------ | :----------------------------------------------------------------------------- |
| Duration | Length of the generated video in seconds. |
| Size | Resolution and aspect ratio of the generated video (e.g. 1280×720, 1920×1080). |
## Audio
| Configuration | Description |
| :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------- |
| Voice | Selects the speaker voice used for audio synthesis (e.g. alloy, echo, nova). Each voice has a distinct tone and style. |
| Format | Output audio file format (e.g. mp3, wav, opus). Affects file size, compatibility, and audio fidelity. |
| Language | Language of the synthesized speech output. Determines pronunciation, accent, and linguistic rendering. |
| Speed | Playback speed of the generated audio. Values above 1.0 increase speed; below 1.0 slow it down. Default: 1.0. |
| Max Tokens | Maximum number of tokens from the input text processed per request. Limits the length of text that can be converted to speech in a single call. |
# Parse & Chat
Source: https://docs.sciforium.com/parse-and-chat
Parse files via the Sciforium API, extract their text, and ask any LLM a question about the content.
## Overview
This guide walks you through parsing one or more files with the Sciforium API, extracting plain text from the results, and sending that text to an LLM with a question.
| Step | What happens |
| ---- | ---------------------------------------------------------------- |
| 1 | **Configure** — set your file path, question, model, and API key |
| 2 | **Install** dependencies |
| 3 | **Initialize** the Sciforium client |
| 4a | **Parse** a single file |
| 4b | \*(Optional)\***Batch-parse** multiple files in parallel |
| 5 | \*(Optional)\***Inspect** the raw parse response |
| 6 | **Extract** plain text from the parse results |
| 7 | **Chat** — send the extracted text + your question to the LLM |
## Prerequisites
* A **Sciforium API key** — get one at [console.sciforium.com](https://console.sciforium.com)
* **Python 3.8+**
* The file(s) you want to parse accessible on disk (or uploaded to Colab — see the note below)
If you're running in **Google Colab**, click the folder icon in the left sidebar, upload your file, then right-click it and select **Copy path** to use in your code. Uploaded files are deleted when the runtime disconnects.
## Step 1 — Configuration
Set the four variables below before running anything else.
* `FILE_PATH` — absolute path to the file you want to parse. Supported formats: PDF, DOCX, DOC, TXT, MD, CSV, HTML, JSON - any `utf-8` encoded file.
* `QUESTION` — the question you want to ask the LLM about the document.
* `MODEL` — the LLM model identifier (e.g. `openai/gpt-oss-120b`, `anthropic/claude-sonnet-4-6`).
* `SCIFORIUM_API_KEY` — your Sciforium API key.
```python theme={null}
FILE_PATH = "/content/sample_data/sample.pdf"
QUESTION = "Extract the candidate's email address and latest company."
MODEL = "openai/gpt-oss-120b"
SCIFORIUM_API_KEY = "your-api-key-here"
```
Never commit your API key to version control. Use environment variables or a secrets manager in production.
## Step 2 — Install dependencies
Run this once if `openai` or `requests` aren't already installed.
```bash theme={null}
pip install openai requests
```
## Step 3 — Initialize the client
This constructs the Sciforium endpoint URLs and resolves the API key, falling back to the `SCIFORIUM_API_KEY` environment variable if set.
```python theme={null}
import base64, os
from pathlib import Path
import requests
from openai import OpenAI
BASE_URL = os.environ.get("SCIFORIUM_BASE_URL", "https://api.sciforium.com").rstrip("/")
PARSE_API_URL = BASE_URL + "/api/attachments/parse"
LLM_BASE_URL = BASE_URL + "/v1"
API_KEY = os.environ.get("SCIFORIUM_API_KEY", SCIFORIUM_API_KEY)
assert API_KEY, "Set SCIFORIUM_API_KEY in the Config cell or as an environment variable."
```
## Step 4a — Parse a single file
Reads the file from `FILE_PATH`, base64-encodes it, and POSTs it to the Sciforium parse endpoint. The response contains structured content (pages, text, metadata) for the file.
```python theme={null}
import time
MIME_MAP = {
"pdf": "application/pdf",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"doc": "application/msword",
"txt": "text/plain",
"md": "text/markdown",
"csv": "text/csv",
"html": "text/html",
"json": "application/json",
"png": "image/png",
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
}
path = Path(FILE_PATH)
mime = MIME_MAP.get(path.suffix.lstrip(".").lower(), "application/octet-stream")
encoded = base64.b64encode(path.read_bytes()).decode()
print(f"Parsing '{path.name}' ({path.stat().st_size // 1024} KB)...")
start_t = time.time()
resp = requests.post(
PARSE_API_URL,
headers={"Content-Type": "application/json", "x-api-key": API_KEY},
json={
"files": [{
"url": f"data:{mime};base64,{encoded}",
"filename": path.name,
"media_type": mime,
}]
},
timeout=300,
)
elapsed = time.time() - start_t
resp.raise_for_status()
parse_response = resp.json()
meta = parse_response.get("metadata", {})
print(
f"Done – files={meta.get('total_files', 1)} | "
f"completed={meta.get('completed', '?')} | "
f"failed={meta.get('failed', 0)} | "
f"time={meta.get('total_processing_time_ms', '?')}ms | "
f"wall={elapsed:.2f}s"
)
```
## Step 4b — Batch parse (optional)
Use this instead of Step 4a to parse **multiple files in parallel** with a thread pool. Add all your file paths to `FILE_PATHS`.
Run either Step 4a **or** Step 4b — not both. If you use this batch step, update Step 6 to iterate over `parse_responses` (plural) instead of `parse_response`.
```python theme={null}
from concurrent.futures import ThreadPoolExecutor, as_completed
FILE_PATHS = [
"/content/sample_data/sample1.pdf",
"/content/sample_data/sample2.pdf",
# Add more file paths here
]
def parse_single_file(file_path):
path = Path(file_path)
mime = MIME_MAP.get(path.suffix.lstrip(".").lower(), "application/octet-stream")
encoded = base64.b64encode(path.read_bytes()).decode()
print(f"Parsing '{path.name}' ({path.stat().st_size // 1024} KB)...")
start_t = time.time()
resp = requests.post(
PARSE_API_URL,
headers={"Content-Type": "application/json", "x-api-key": API_KEY},
json={
"files": [{
"url": f"data:{mime};base64,{encoded}",
"filename": path.name,
"media_type": mime,
}]
},
timeout=300,
)
elapsed = time.time() - start_t
resp.raise_for_status()
parse_response = resp.json()
meta = parse_response.get("metadata", {})
print(
f"Done – '{path.name}' | "
f"completed={meta.get('completed', '?')} | "
f"failed={meta.get('failed', 0)} | "
f"time={meta.get('total_processing_time_ms', '?')}ms | "
f"wall={elapsed:.2f}s"
)
return parse_response
parse_responses = []
with ThreadPoolExecutor(max_workers=min(8, len(FILE_PATHS))) as executor:
futures = {executor.submit(parse_single_file, fp): fp for fp in FILE_PATHS}
for future in as_completed(futures):
parse_responses.append(future.result())
```
## Step 5 — Inspect the raw response (optional)
Print the full JSON to explore the response schema or debug issues. You can skip this step — it has no side effects.
```python theme={null}
import json
print(json.dumps(parse_response, indent=2))
```
## Step 6 — Extract text from parse results
This walks the parse response and stitches all page text into a single `document_text` string. Pages are labeled `[Page N]` so the LLM can reference them. Files that failed to parse are skipped with a warning.
```python theme={null}
texts = []
for result in parse_response.get("results", []):
if result.get("status") not in ("success", "completed"):
print(f"Warning: '{result.get('filename')}' status={result.get('status')}, skipping.")
continue
raw = result.get("content") or {}
if not isinstance(raw, dict):
raw = {"text": raw}
pages = raw.get("pages") or []
if pages:
texts.append(
"\n\n".join(
f"[Page {p.get('page', '?')}]\n{p['text'].strip()}"
for p in pages
if p.get("text", "").strip()
)
)
print(f" '{result['filename']}': {len(pages)} pages")
elif raw.get("text", "").strip():
texts.append(raw["text"].strip())
document_text = "\n\n".join(texts)
assert document_text, "No text extracted."
print(f"Extracted ~{len(document_text):,} characters.")
```
**You are responsible for context management beyond this point.** If `document_text` is larger than your model's context window, you must truncate, chunk, or summarize it before sending. For large documents, consider splitting by page and processing in batches, or using a retrieval step (e.g. embeddings + vector search) to select only the relevant sections.
## Step 7 — Ask the LLM
Send `document_text` plus your `QUESTION` to the configured model via the Sciforium OpenAI-compatible gateway.
```python theme={null}
client = OpenAI(api_key=API_KEY, base_url=LLM_BASE_URL)
response = client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "system",
"content": "You are a helpful assistant. Answer questions based on the document content provided. Be concise and accurate.",
},
{
"role": "user",
"content": f"\n{document_text}\n\n\n{QUESTION}",
},
],
)
answer = response.choices[0].message.content
print(answer)
```
To ask multiple questions without re-parsing, just change `QUESTION` and re-run this cell.
# Quickstart
Source: https://docs.sciforium.com/quickstart
Get your first inference response in under 5 minutes.
Go to sciforium.com and sign up with email/password or Google SSO. Note: Sciforium is a prepaid platform — you'll need to purchase credits before making your first API call.
Navigate to Billing in the top navigation bar and add credits via Stripe. The minimum purchase is \$5.
1. Navigate to API Keys in the left sidebar.
2. Click + Create API Key in the top right.
3. Give your key a name and click Create.
4. Copy your key immediately — it is displayed only once.
Replace `YOUR_API_KEY` with the key you just created.
```bash theme={null}
curl https://api.sciforium.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-r1-distill-llama-8b",
"messages": [
{ "role": "user", "content": "Explain what Sciforium does in one sentence." }
]
}'
```
You should receive a JSON response with the model's completion. You're live.
# Security Best Practices
Source: https://docs.sciforium.com/security-best-practices
Never hardcode API keys in source code or commit them to version control.
Use one key per environment (dev, staging, production) so you can revoke them independently.
Revoke keys you no longer use immediately.
Monitor the Analytics dashboard for unexpected usage spikes, which may indicate an exposed key.
# Support & Feedback
Source: https://docs.sciforium.com/support-and-feedback
## Submitting a Support Request
For urgent production issues, include request IDs, error messages, the model in use, and any relevant timestamps.
## Submitting Feedback
Feedback is reviewed by the Sciforium team and used to inform the product roadmap.
# User Management
Source: https://docs.sciforium.com/user-management
Admins can manage organization members from the User Management page in the top navigation bar.
Click **+ Invite User** in the top right, enter the user's email, and select a role (Admin or Developer). Click **Send Invite** to send them an email with a unique acceptance link.
Use the role dropdown next to the user's name. Changes take effect immediately.
Use the options menu next to the user's entry. Removed users immediately lose access to the organization.
Invited users with no existing account are prompted to register. Users with existing accounts are added to the organization automatically.
# Video Generation APIs
Source: https://docs.sciforium.com/video-apis
# Video APIs
## Overview
Video generation is asynchronous. Use this lifecycle:
1. **Submit** a job (`POST /v1/videos`)
2. **Poll** status (`GET /v1/videos/{id}`)
3. **Download** output (`GET /v1/videos/{id}/content`)
## Base URL
`https://api.sciforium.com/v1`
## Authentication
Include your API token in the headers:
* `Authorization: Bearer `
* `x-api-key: `
***
## Supported Endpoints
| Method | Path | Purpose |
| -------- | ---------------------- | ---------------------------- |
| `POST` | `/videos` | Create a video job |
| `GET` | `/videos/{id}` | Get video job status |
| `GET` | `/videos/{id}/content` | Download video content (MP4) |
| `GET` | `/videos` | List video jobs |
| `DELETE` | `/videos/{id}` | Delete a video job |
***
## 1) Create Video Job
**Method:** `POST`\
**Endpoint:** `https://api.sciforium.com/v1/videos`\
**Content-Type:** `multipart/form-data`\
**Success Status:** `201 Created`
### Request fields
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | --------------------------------------------------------------------------------------------------- |
| `prompt` | string | Yes | Prompt text (`1..32000` chars). |
| `model` | string | Yes | Video model ID. |
| `seconds` | string | No | Duration in seconds: `2`, `4`, `8`, `12`. Default: `4`. |
| `size` | string | No | One of `720x1280`, `1280x720`, `1024x1792`, `1792x1024`, `640x480`, `480x640`. Default: `720x1280`. |
| `input_reference` | file | No | Optional reference image for image-to-video flows. |
### Example cURL (text-to-video)
```bash theme={null}
curl -X POST "https://api.sciforium.com/v1/videos" \
-H "Authorization: Bearer $TOKEN" \
-H "x-api-key: $TOKEN" \
-F "model=Wan-AI/Wan2.2-T2V-A14B-Diffusers" \
-F "prompt=A cinematic drone shot over mountain ridges at sunrise" \
-F "seconds=4" \
-F "size=640x480"
```
***
## 2. Check the Status: GET /v1/videos/
Since you don't know exactly when the video will be finished, you "poll" this endpoint (requesting it every 5–10 seconds) or wait for a **Webhook** notification if you have one configured.
### The Response
The status field is the most important part of this response.
| **Status** | **Meaning** |
| :----------- | :-------------------------------------------- |
| queued | Waiting for available compute. |
| in\_progress | The model is currently rendering frames. |
| completed | The video is ready for download. |
| failed | Something went wrong (check the error field). |
**Example of a completed status:**
```json theme={null}
{
"id": "vid_abc123xyz",
"status": "completed",
"progress": 1.0,
"expires_at": 1775701400
}
```
**Note:** Most completed videos are only stored on OpenAI's servers for **24 hours** before they are deleted for privacy and storage reasons.
***
## 3. Retrieve the File: GET /v1/videos//content
Once the status is completed, you call this final endpoint to get the actual media.
### The Response
Unlike the other two endpoints which return JSON, this endpoint returns the **binary data** of the video file (usually an .mp4).
**Content-Type**: video/mp4 **Behavior**: In a browser or code, this will trigger a download or allow you to stream the bytes directly into a file buffer.
***