HTTP Manifest Providers (Outbound)¶
Faxbot supports a lightweight outbound provider type defined by a JSON manifest. This lets you integrate REST fax APIs without writing server code.
Plugins Overview Curated Registry Plugin Config File
Concepts¶
 Manifest
: Describes send_fax and optional get_status/cancel_fax actions
Runtime : Interprets the manifest, applies auth, renders bodies, and maps responses
Allowed domains : Explicit host allowlist per manifest to reduce SSRF risk
Manifest schema¶
 id
: Provider ID (e.g., acmefax)
 name
: Display name
 auth
: { scheme: none|basic|bearer|api_key_header|api_key_query, ... }
 traits
: Optional traits that describe runtime needs and behavior. Traits override defaults in config/provider_traits.json.
  Example keys: kind (cloud|self_hosted), requires_ghostscript, requires_tiff, supports_inbound, inbound_verification (hmac|none|internal_secret), needs_storage, outbound_status_only.
 allowed_domains
: Array of hostnames
 timeout_ms
: Request timeout
 actions
: - send_fax: { method, url, headers, body: { kind: json|form|multipart|none, template }, path_params: [], response: { job_id, status, error?, status_map? } }
  - get_status (optional): { ... }
Full example (traits‑first)¶
{
  // id: Provider ID (e.g., "acmefax")
  "id": "ringcentral",
  // name: Display name
  "name": "RingCentral Fax API",
  // auth: how requests are authenticated
  //   scheme: none | basic | bearer | api_key_header | api_key_query | ...
  "auth": { "scheme": "bearer" },
  // traits: runtime needs/behavior; overrides defaults in config/provider_traits.json
  //   kind: "cloud" | "self_hosted"
  //   requires_ghostscript, requires_tiff, supports_inbound, needs_storage (true|false)
  //   inbound_verification: "hmac" | "none" | "internal_secret"
  //   outbound_status_only: true|false
  "traits": {
    "kind": "cloud",
    "requires_ghostscript": true,
    "requires_tiff": false,
    "supports_inbound": false,
    "inbound_verification": "none",
    "needs_storage": false,
    "outbound_status_only": false
  },
  // allowed_domains: explicit host allowlist to reduce SSRF risk
  "allowed_domains": ["platform.ringcentral.com"],
  // timeout_ms: request timeout in milliseconds
  "timeout_ms": 15000,
  // actions: outbound capabilities.
  // send_fax: method/url/headers and body; response maps provider fields
  // get_status: optional lookup for a given fax/job id
  "actions": {
    "send_fax": {
      "method": "POST",
      "url": "https://platform.ringcentral.com/restapi/v1.0/account/~/extension/~/fax",
      "headers": {},
      "body": {
        // kind: json | form | multipart | none
        "kind": "multipart",
        // template variables available: {{to}}, {{from}}, {{file}}, {{file_url}}, {{settings}}, {{creds}}
        "template": "request={\\"to\\":[{\\"phoneNumber\\":\\"{{to}}\\"}]}&attachment={{file}}"
      },
      "path_params": [],
      "response": {
        // job id path in provider response
        "job_id": "id",
        // optional status mapping
        "status": "messageStatus",
        "status_map": { "Queued": "queued", "Processing": "in_progress", "Sent": "SUCCESS", "Error": "FAILED" },
        // optional error field
        "error": "error"
      }
    },
    "get_status": {
      "method": "GET",
      "url": "https://platform.ringcentral.com/restapi/v1.0/account/~/message-store/{{fax_id}}",
      "headers": {},
      "body": { "kind": "none", "template": "" },
      "response": { "status": "messageStatus" }
    }
  }
}
Templates¶
 Interpolation
: Mustache‑style {{ var }} against a context with to, from, file_url, file_path, settings, and creds
Multipart example¶
# body.kind = "multipart"
# body.template: request form fields, special key 'attachment'/'file' carries the PDF part
request={"to":[{"phoneNumber":"{{to}}"}]}&attachment={{file}}
Response mapping¶
 response.job_id
: JSON path to the provider’s job ID (e.g., data.id)
 response.status
: JSON path to status (e.g., status)
 response.status_map
: Optional map to normalize provider values to queued|in_progress|SUCCESS|FAILED
 response.error
: Optional JSON path for human‑readable error
Security¶
 Allowed domains
: Every manifest must include an allowed_domains array; requests to other hosts are blocked
Secrets : Not stored in the manifest; provide credentials via Admin Console or environment
Install & configure¶
- Place 
manifest.jsonunderconfig/providers/<id>/manifest.json - Enable v3 plugins: 
FEATURE_V3_PLUGINS=true - Persist selection via Admin Console → Plugins, or 
PUT /plugins/{id}/config - Apply credentials in Settings and test with the console’s Send tab
 
Admin API helpers¶
POST /admin/plugins/http/install— install a single manifest (body or URL)POST /admin/plugins/http/import-manifests— bulk import manifests from a JSON file or URLPOST /admin/plugins/http/validate— validate a manifest without installing
Notes¶
- Manifests are evaluated server‑side; no UI or browser secrets are required
 - For HIPAA, disable dynamic install and keep manifests under version control
 - The server also discovers manifests under 
config/providers/<id>/manifest.json 
End‑to‑End: Validate → Install → Activate → Send¶
1) Paste JSON in Admin → Plugins → HTTP Manifest Tester
   - Validate: POST /admin/plugins/http/validate (api/app/main.py:2556, 2570‑2579)
   - Dry‑run: same with render_only:false (api/app/main.py:2583‑2588)
2) Install to disk
   - Writes config/providers/<id>/manifest.json (api/app/main.py:2530‑2538)
   - Diagnostics list manifests and basic issues (api/app/main.py:3714, 3721‑3756)
3) Activate backend
   - FEATURE_V3_PLUGINS=true and OUTBOUND_BACKEND=<id> (api/app/config.py:380‑385)
   - Or PUT /plugins/{id}/config (api/app/main.py:6288‑6323), then restart (api/app/main.py:6322)
4) Runtime
   - Send path uses manifest when present (api/app/main.py:4086, 4093‑4153; 5045‑5090)
   - Status refresh via POST /admin/fax-jobs/{job_id}/refresh (api/app/main.py:3496‑3608)
RAG/MCP: You can ask the code RAG things like “show the manifest schema” or “where is install endpoint” and get citations. See rag‑service docs for MCP setup.