SingleFax APIドキュメントとCLI
APIキーと前払いクレジットを使用。npmの公式CLI、またはcurl・Node・Python・Go・PHPのRESTサンプルを利用できます。チャットアプリはMCPを使ってください。
CLI
npx @singlefax/cli send FILE --to +E164。Word・Excel・PDF・画像は元の形式で渡し、先にPDFに変換しないでください。
REST
POST /api/v1/faxesにBearer sf_live_…とIdempotency-Keyを指定。
クイックスタート
- APIキーでスコープを設定したキーを作成(
fax:send、fax:read、inbox:read、credits:read)。 - 管理画面でクレジットを前払い、または
npx @singlefax/cli credits buy。 - 下のCLIで送信するか、
POST /api/v1/faxes。
ベースURLはhttps://api.singlefax.com(https://singlefax.comも同じパス)。sf_test_キーで実送信はできません。キーをブラウザーに置かないでください。
CLI
Node 20以上。設定:SINGLEFAX_API_KEYまたは~/.config/singlefax/config.json。元のファイル(Word、Excel、PowerPoint、PDF、テキスト、画像)を渡してください。先にPDFへ変換しないでください。MCP OAuthやStripe MPP用ではありません。
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 20ファクスを送信
1回のリクエストで、base64形式の文書と宛先を送ります。PDF、Word、Excel、PowerPoint、テキスト、画像に対応。Idempotency-Keyが必須です。同じキーで異なる本文を再送すると409を返します。
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);大きいファイルはアップロードして送信
CLIはアップロードトークンを作成し、バイト列をPUTしてからuploadTokenで送信します。PUTにもBearerトークンが必要です。大きなファイルをJSONに詰め込むより、この方法を推奨します。先にPDFへ変換しないでください。
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 APIリファレンス
| 方法 | パス | 認証 | 備考 |
|---|---|---|---|
| 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. |
完全なスキーマ:openapi.json。互換性を損なう変更:バージョン管理ポリシー(Sunsetは90日後)。このクレジットAPIでのHTTP 402は残高不足です。MPPの402はPOST /api/v1/machine/faxesのみです。
Webhook
署名付きのfax.queued、fax.delivered、fax.failed、credits.low_balanceイベント。生の本文に対してSingleFax-Signature(t=,v1=)を検証してください。timestamp.payloadのHMAC-SHA256です。管理画面のWebhookでエンドポイントを追加します。
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"])制約事項
- APIキーはサーバーで管理し、
sf_live_をウェブページに埋め込まないでください。 - PHIは送らないでください。対象機関は、電子BAA付きのウェブサイトのHIPAA Secureモードを利用してください。
- 有効なE.164形式の宛先。PDF、Word、Excel、PowerPoint、RTF、テキスト、画像に対応します。MIMEタイプはOpenAPIをご覧ください。先にPDF変換しないでください。送信前に当社で変換します。
- チャット用MCPとStripe MPP:/agents、mpp.md、MPPと即時決済の違い。
よくあるご質問
機械可読ドキュメント
クローラー・コーディングエージェント向けのMarkdownとOpenAPI。内容はこのページと同じです。