SingleFax API belgeleri ve CLI
API anahtarı ve ön ödemeli bakiye. npm’de resmi CLI veya curl, Node, Python, Go, PHP için doğrudan kullanılabilir REST örnekleri. Sohbet uygulamaları MCP kullanmalıdır.
CLI
npx @singlefax/cli send FILE --to +E164; özgün Word, Excel, PDF veya görsel dosyasıyla kullanın, önceden PDF’ye dönüştürmeyin.
REST
Bearer sf_live_… ve Idempotency-Key ile POST /api/v1/faxes.
Hızlı başlangıç
- API anahtarları üzerinden kapsamı sınırlı anahtar oluşturun (
fax:send,fax:read,inbox:read,credits:read). - Panelden veya
npx @singlefax/cli credits buyüzerinden ön ödemeli bakiye yükleyin. - Aşağıdaki CLI ile veya
POST /api/v1/faxesüzerinden gönderin.
Temel URL https://api.singlefax.com (https://singlefax.com üzerinde aynı yollar). sf_test_ anahtarları gerçek gönderim yapamaz. Anahtarları tarayıcıya koymayın.
CLI
Node 20+. Yapılandırma: SINGLEFAX_API_KEY veya ~/.config/singlefax/config.json. Özgün dosyayı iletin (Word, Excel, PowerPoint, PDF, metin veya görsel). Önceden PDF’ye dönüştürmeyin. MCP OAuth veya Stripe MPP için değildir.
export SINGLEFAX_API_KEY=sf_live_…
npx @singlefax/cli send ./document.docx --to +14155552671
npx @singlefax/cli send ./document.pdf --to +14155552671 --wait --jsonnpx @singlefax/cli quote --to +14155552671 --pages 12
npx @singlefax/cli status <id>
npx @singlefax/cli list
npx @singlefax/cli numbers
npx @singlefax/cli numbers buy
npx @singlefax/cli credits
npx @singlefax/cli credits buy --dollars 20Faks gönder
Tek istek: base64 belge ve hedef. PDF, Word, Excel, PowerPoint, metin veya görsel. Idempotency-Key zorunludur. Aynı anahtarı farklı gövdeyle kullanmak 409 döndürür.
PDF_B64="$(base64 < document.pdf | tr -d '\n')"
curl -sS https://api.singlefax.com/api/v1/faxes \
-H "Authorization: Bearer $SINGLEFAX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{\"to\":\"+14155552671\",\"base64\":\"$PDF_B64\"}"import { readFile } from 'node:fs/promises'
import { randomUUID } from 'node:crypto'
const api = 'https://api.singlefax.com'
const key = process.env.SINGLEFAX_API_KEY
const pdf = await readFile('./document.pdf')
const res = await fetch(`${api}/api/v1/faxes`, {
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({
to: '+14155552671',
base64: pdf.toString('base64'),
}),
})
console.log(await res.json())import base64, json, os, urllib.request, uuid
from pathlib import Path
API = "https://api.singlefax.com"
KEY = os.environ["SINGLEFAX_API_KEY"]
pdf = Path("document.pdf").read_bytes()
req = urllib.request.Request(
f"{API}/api/v1/faxes",
data=json.dumps({
"to": "+14155552671",
"base64": base64.b64encode(pdf).decode(),
}).encode(),
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
method="POST",
)
with urllib.request.urlopen(req) as res:
print(res.read().decode())package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
pdf, err := os.ReadFile("document.pdf")
if err != nil {
panic(err)
}
body, _ := json.Marshal(map[string]string{
"to": "+14155552671",
"base64": base64.StdEncoding.EncodeToString(pdf),
})
req, _ := http.NewRequest("POST", "https://api.singlefax.com/api/v1/faxes", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("SINGLEFAX_API_KEY"))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", "send-document-1")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}<?php
$pdf = base64_encode(file_get_contents('document.pdf'));
$ch = curl_init('https://api.singlefax.com/api/v1/faxes');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('SINGLEFAX_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: send-document-1',
],
CURLOPT_POSTFIELDS => json_encode(['to' => '+14155552671', 'base64' => $pdf]),
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);Büyük dosyalar: önce yükleyin, sonra gönderin
CLI’nin yaptığı işlemler: yükleme belirteci oluşturmak, baytları PUT ile yüklemek, ardından uploadToken ile göndermek. PUT da Bearer belirteci gerektirir. Büyük dosyayı JSON’a gömmek yerine bu yöntemi kullanın. Önceden PDF’ye dönüştürmeyin.
UPLOAD=$(curl -sS https://api.singlefax.com/api/v1/uploads \
-H "Authorization: Bearer $SINGLEFAX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType":"application/pdf"}')
UPLOAD_URL=$(printf '%s' "$UPLOAD" | python3 -c "import json,sys; print(json.load(sys.stdin)['uploadUrl'])")
TOKEN=$(printf '%s' "$UPLOAD" | python3 -c "import json,sys; print(json.load(sys.stdin)['uploadToken'])")
curl -sS -X PUT "$UPLOAD_URL" \
-H "Authorization: Bearer $SINGLEFAX_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @document.pdf
curl -sS https://api.singlefax.com/api/v1/faxes \
-H "Authorization: Bearer $SINGLEFAX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{\"to\":\"+14155552671\",\"uploadToken\":\"$TOKEN\"}"import { readFile } from 'node:fs/promises'
import { randomUUID } from 'node:crypto'
const api = 'https://api.singlefax.com'
const key = process.env.SINGLEFAX_API_KEY
const auth = { Authorization: `Bearer ${key}` }
const pdf = await readFile('./document.pdf')
const upload = await fetch(`${api}/api/v1/uploads`, {
method: 'POST',
headers: { ...auth, 'Content-Type': 'application/json' },
body: JSON.stringify({ contentType: 'application/pdf', maxBytes: pdf.byteLength }),
}).then((r) => r.json())
await fetch(upload.uploadUrl, {
method: 'PUT',
headers: { ...auth, 'Content-Type': 'application/pdf' },
body: pdf,
})
const fax = await fetch(`${api}/api/v1/faxes`, {
method: 'POST',
headers: {
...auth,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({ to: '+14155552671', uploadToken: upload.uploadToken }),
}).then((r) => r.json())
console.log(fax)import json, os, urllib.request, uuid
from pathlib import Path
API = "https://api.singlefax.com"
KEY = os.environ["SINGLEFAX_API_KEY"]
pdf = Path("document.pdf").read_bytes()
def request(url, *, data=None, method="GET", headers=None, content_type="application/json"):
req = urllib.request.Request(
url,
data=data,
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": content_type,
**(headers or {}),
},
method=method,
)
with urllib.request.urlopen(req) as res:
return res.read()
upload = json.loads(request(
f"{API}/api/v1/uploads",
method="POST",
data=json.dumps({"contentType": "application/pdf", "maxBytes": len(pdf)}).encode(),
))
request(upload["uploadUrl"], method="PUT", data=pdf, content_type="application/pdf")
fax = request(
f"{API}/api/v1/faxes",
method="POST",
data=json.dumps({"to": "+14155552671", "uploadToken": upload["uploadToken"]}).encode(),
headers={"Idempotency-Key": str(uuid.uuid4())},
)
print(fax.decode())package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
key := os.Getenv("SINGLEFAX_API_KEY")
pdf, err := os.ReadFile("document.pdf")
if err != nil {
panic(err)
}
upBody, _ := json.Marshal(map[string]any{"contentType": "application/pdf", "maxBytes": len(pdf)})
upReq, _ := http.NewRequest("POST", "https://api.singlefax.com/api/v1/uploads", bytes.NewReader(upBody))
upReq.Header.Set("Authorization", "Bearer "+key)
upReq.Header.Set("Content-Type", "application/json")
upRes, err := http.DefaultClient.Do(upReq)
if err != nil {
panic(err)
}
defer upRes.Body.Close()
var upload struct {
UploadToken string `json:"uploadToken"`
UploadUrl string `json:"uploadUrl"`
}
json.NewDecoder(upRes.Body).Decode(&upload)
put, _ := http.NewRequest("PUT", upload.UploadUrl, bytes.NewReader(pdf))
put.Header.Set("Authorization", "Bearer "+key)
put.Header.Set("Content-Type", "application/pdf")
if _, err := http.DefaultClient.Do(put); err != nil {
panic(err)
}
sendBody, _ := json.Marshal(map[string]string{"to": "+14155552671", "uploadToken": upload.UploadToken})
faxReq, _ := http.NewRequest("POST", "https://api.singlefax.com/api/v1/faxes", bytes.NewReader(sendBody))
faxReq.Header.Set("Authorization", "Bearer "+key)
faxReq.Header.Set("Content-Type", "application/json")
faxReq.Header.Set("Idempotency-Key", "send-document-1")
faxRes, err := http.DefaultClient.Do(faxReq)
if err != nil {
panic(err)
}
defer faxRes.Body.Close()
out, _ := io.ReadAll(faxRes.Body)
fmt.Println(string(out))
}<?php
$key = getenv('SINGLEFAX_API_KEY');
$pdf = file_get_contents('document.pdf');
$ch = curl_init('https://api.singlefax.com/api/v1/uploads');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $key",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['contentType' => 'application/pdf', 'maxBytes' => strlen($pdf)]),
CURLOPT_RETURNTRANSFER => true,
]);
$upload = json_decode(curl_exec($ch), true);
$ch = curl_init($upload['uploadUrl']);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $key",
'Content-Type: application/pdf',
],
CURLOPT_POSTFIELDS => $pdf,
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
$ch = curl_init('https://api.singlefax.com/api/v1/faxes');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer $key",
'Content-Type: application/json',
'Idempotency-Key: send-document-1',
],
CURLOPT_POSTFIELDS => json_encode(['to' => '+14155552671', 'uploadToken' => $upload['uploadToken']]),
CURLOPT_RETURNTRANSFER => true,
]);
echo curl_exec($ch);REST başvuru belgeleri
| Yöntem | Yol | Kimlik doğrulama | Notlar |
|---|---|---|---|
| POST | /api/v1/faxes | fax:send | Idempotency-Key required. JSON { to, base64 | uploadToken } or multipart file. |
| GET | /api/v1/faxes | fax:read | List. Query limit, offset. |
| GET | /api/v1/faxes/{id} | fax:read | Status including latest delivery attempt. |
| POST | /api/v1/faxes/quote | fax:read | { to, pages? | base64? }, quote without charging. |
| POST | /api/v1/uploads | fax:send | Returns uploadToken + uploadUrl. PUT the bytes, then send with uploadToken. |
| GET | /api/v1/credits | credits:read | Prepaid balance. |
| POST | /api/v1/credits/checkout | credits:checkout | { amountCents }. Stripe Checkout for a credit pack. |
| GET | /api/v1/received-faxes | inbox:read | Inbound inbox list. Query limit, cursor. No transcripts in list. |
| GET | /api/v1/received-faxes/{id} | inbox:read | Inbound detail with bounded transcript slice. HIPAA Secure content is omitted. |
| GET | /api/v1/numbers | fax:read | Active fax numbers. |
| POST | /api/v1/numbers | credits:checkout | { sku?, country?, state? }, default inbound_30day. Returns checkoutUrl + reserved phoneNumber. |
| GET | /api/v1/skus | public | Catalog. Same shape as MCP list_skus. |
| POST | /api/v1/checkout | credits:checkout | { sku }, credit_pack, inbound, subscriptions, lifetime. |
Tam şemalar: openapi.json. Geriye uyumsuz değişiklikler: sürüm politikası (90 günlük Sunset süresi). Bu bakiye API’sinde HTTP 402 yetersiz bakiye anlamına gelir. MPP 402 yalnızca POST /api/v1/machine/faxes içindir.
Webhook’lar
İmzalı fax.queued, fax.delivered, fax.failed ve credits.low_balance olayları. SingleFax-Signature (t=,v1=) doğrulamasını ham gövde üzerinde yapın. timestamp.payload için HMAC-SHA256 kullanılır. paneldeki webhook’lar bölümünden uç nokta ekleyin.
import { createHmac, timingSafeEqual } from 'node:crypto'
export function verifySingleFaxSignature(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(signatureHeader.split(',').map((kv) => kv.split('=')))
const timestamp = Number(parts.t)
if (!Number.isFinite(timestamp) || !parts.v1) return false
if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) return false
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')
const a = Buffer.from(parts.v1)
const b = Buffer.from(expected)
return a.length === b.length && timingSafeEqual(a, b)
}import hashlib, hmac, time
def verify_singlefax_signature(payload: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
timestamp = int(parts["t"])
if abs(int(time.time()) - timestamp) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{timestamp}.".encode() + payload,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Kısıtlamalar
- API anahtarlarını sunucuda tutun.
sf_live_anahtarını web sayfasına gömmeyin. - PHI göndermeyin. Kapsam dahilindeki kuruluşlar web sitesinde elektronik BAA ile HIPAA Secure modunu kullanır.
- Geçerli E.164 hedefleri. PDF, Word, Excel, PowerPoint, RTF, düz metin ve görseller; MIME türleri için OpenAPI’ye bakın. Önceden PDF’ye dönüştürmeyin; gönderimden önce biz dönüştürürüz.
- Sohbet MCP’si ve Stripe MPP: /agents, mpp.md ve MPP ve Instant Checkout karşılaştırması.
Sık sorulan sorular
Makinece okunabilir belgeler
Tarayıcı robotları ve kodlama ajanları için düz Markdown ve OpenAPI. Bu sayfayla aynı bilgileri içerir.