{
  "openapi": "3.1.0",
  "info": {
    "title": "unofax x402 API",
    "version": "v1",
    "description": "Create a fax job, upload a document, poll until it is ready, then pay with x402 to send it. Typical agent flow: POST /api/x402, PUT the raw file bytes to uploadUrl, poll GET /api/x402/{jobId} until status is ready_to_send, POST /api/x402/{jobId}/send once without PAYMENT-SIGNATURE to receive a 402 challenge, retry the same POST with PAYMENT-SIGNATURE, then poll GET /api/x402/{jobId} until status is sent, retrying, or failed."
  },
  "servers": [
    {
      "url": "https://unofax.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "x402",
      "description": "AI-agent-oriented fax sending flow using x402 payments."
    }
  ],
  "paths": {
    "/api/x402": {
      "get": {
        "tags": ["x402"],
        "operationId": "getX402Descriptor",
        "summary": "Describe the x402 API",
        "description": "Returns a compact machine-readable descriptor that points agents to the create, poll, send, and OpenAPI endpoints.",
        "responses": {
          "200": {
            "description": "Machine-readable x402 API descriptor.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/X402Descriptor"
                },
                "examples": {
                  "default": {
                    "value": {
                      "name": "unofax x402 API",
                      "version": "v1",
                      "description": "Create a fax job, upload a document, wait for conversion, then pay with x402 to send it.",
                      "openapi": "/api/x402/openapi.json",
                      "create": {
                        "method": "POST",
                        "path": "/api/x402"
                      },
                      "status": {
                        "method": "GET",
                        "path": "/api/x402/{jobId}"
                      },
                      "send": {
                        "method": "POST",
                        "path": "/api/x402/{jobId}/send"
                      },
                      "flow": [
                        "POST /api/x402",
                        "PUT uploadUrl",
                        "GET /api/x402/{jobId} until status=ready_to_send",
                        "POST /api/x402/{jobId}/send",
                        "retry same POST with PAYMENT-SIGNATURE after 402",
                        "GET /api/x402/{jobId} until status=sent, retrying, or failed"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["x402"],
        "operationId": "createX402Job",
        "summary": "Create an x402 fax job",
        "description": "Creates a fax job and returns a signed upload URL. Upload the raw file bytes with HTTP PUT to uploadUrl, then poll statusUrl until the job becomes ready_to_send.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/X402CreateRequest"
              },
              "examples": {
                "simple": {
                  "value": {
                    "faxNumber": "+14155551212",
                    "fileName": "contract.pdf"
                  }
                },
                "scheduledWithCoverSheet": {
                  "value": {
                    "faxNumber": "+14155551212",
                    "fileName": "tax-documents.pdf",
                    "mimeType": "application/pdf",
                    "scheduledTime": "2026-04-12T15:30:00Z",
                    "coverSheet": {
                      "template": "standard",
                      "locale": "en",
                      "senderName": "Jane Smith",
                      "recipientName": "Billing Department",
                      "subject": "Signed contract",
                      "message": "Please process this as soon as possible.",
                      "urgency": "urgent"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created job and signed upload URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/X402CreateResponse"
                },
                "examples": {
                  "default": {
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "status": "awaiting_upload",
                      "uploadUrl": "https://uploads.unofax.com/uploads/019d7a7f-a183-752d-9b3b-11511179033b/contract.pdf?Expires=...",
                      "statusUrl": "/api/x402/019d7a7f-a183-752d-9b3b-11511179033b",
                      "sendUrl": "/api/x402/019d7a7f-a183-752d-9b3b-11511179033b/send"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input. Examples: missing faxNumber, invalid E.164 format, unsupported file type, or cover sheet validation error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "missingFaxNumber": {
                    "value": {
                      "error": "faxNumber is required"
                    }
                  },
                  "invalidDestination": {
                    "value": {
                      "error": "fax number must be in E.164 format"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/x402/openapi.json": {
      "get": {
        "tags": ["x402"],
        "operationId": "getX402OpenApi",
        "summary": "Get this OpenAPI document",
        "responses": {
          "200": {
            "description": "OpenAPI JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "This OpenAPI document."
                }
              }
            }
          }
        }
      }
    },
    "/api/x402/{jobId}": {
      "get": {
        "tags": ["x402"],
        "operationId": "getX402Job",
        "summary": "Get x402 job status",
        "description": "Poll this endpoint after uploading. The response shape varies by status. When status is ready_to_send, the payment object and sendUrl tell the agent exactly how to proceed next.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          }
        ],
        "responses": {
          "200": {
            "description": "Current job status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/X402JobStatusResponse"
                },
                "examples": {
                  "awaitingUpload": {
                    "summary": "The job exists but the document has not been uploaded yet.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "awaiting_upload"
                    }
                  },
                  "processing": {
                    "summary": "The upload was received and conversion is still in progress.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "processing",
                      "schedule": {
                        "scheduledTime": "2026-04-12T15:30:00Z"
                      }
                    }
                  },
                  "readyToSend": {
                    "summary": "The document is converted and ready for payment.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "ready_to_send",
                      "pageCount": 3,
                      "previewUrl": "/preview/019d7a7f-a183-752d-9b3b-11511179033b/contract.pdf",
                      "schedule": {
                        "scheduledTime": "2026-04-12T15:30:00Z"
                      },
                      "payment": {
                        "scheme": "exact",
                        "network": "eip155:8453",
                        "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                        "amountAtomic": "600000",
                        "amountDisplay": "0.6 USDC",
                        "payTo": "0x1111111111111111111111111111111111111111",
                        "expiresAt": "2026-04-12T15:45:00Z"
                      },
                      "sendUrl": "/api/x402/019d7a7f-a183-752d-9b3b-11511179033b/send"
                    }
                  },
                  "sending": {
                    "summary": "Payment was accepted and the fax is queued or in flight.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "sending",
                      "pageCount": 3,
                      "previewUrl": "/preview/019d7a7f-a183-752d-9b3b-11511179033b/contract.pdf"
                    }
                  },
                  "retrying": {
                    "summary": "A temporary delivery error occurred and the fax will be retried automatically.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "retrying",
                      "pageCount": 3,
                      "previewUrl": "/preview/019d7a7f-a183-752d-9b3b-11511179033b/contract.pdf"
                    }
                  },
                  "sent": {
                    "summary": "The fax completed successfully.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "sent",
                      "pageCount": 3,
                      "previewUrl": "/preview/019d7a7f-a183-752d-9b3b-11511179033b/contract.pdf"
                    }
                  },
                  "failed": {
                    "summary": "The fax cannot proceed or finished with an error.",
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "faxNumber": "+14155551212",
                      "status": "failed",
                      "error": "payment expired"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Invalid or expired jobId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "notFound": {
                    "value": {
                      "error": "job not found"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/x402/{jobId}/send": {
      "post": {
        "tags": ["x402"],
        "operationId": "sendX402Job",
        "summary": "Pay and send an x402 fax job",
        "description": "This endpoint is idempotent. Call it once without PAYMENT-SIGNATURE to receive a 402 payment challenge in the PAYMENT-REQUIRED header. After signing the payment, retry the same POST with the PAYMENT-SIGNATURE header. Repeating the same paid request may return the cached settlement in PAYMENT-RESPONSE.",
        "parameters": [
          {
            "$ref": "#/components/parameters/JobId"
          },
          {
            "$ref": "#/components/parameters/PaymentSignature"
          }
        ],
        "responses": {
          "200": {
            "description": "The job was already sent. This can happen when retrying an idempotent request after completion.",
            "headers": {
              "PAYMENT-RESPONSE": {
                "$ref": "#/components/headers/PaymentResponse"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/X402SendAlreadySentResponse"
                },
                "examples": {
                  "alreadySent": {
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "status": "sent"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Payment accepted and fax send queued, or the same request was replayed while the fax is already in flight.",
            "headers": {
              "PAYMENT-RESPONSE": {
                "$ref": "#/components/headers/PaymentResponse"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/X402SendAcceptedResponse"
                },
                "examples": {
                  "sending": {
                    "value": {
                      "jobId": "019d7a7f-a183-752d-9b3b-11511179033b",
                      "status": "sending"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid payment input, such as an invalid PAYMENT-SIGNATURE header.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "invalidPaymentSignature": {
                    "value": {
                      "error": "invalid PAYMENT-SIGNATURE"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment required. This is the expected first response when PAYMENT-SIGNATURE is missing or invalid. Read the PAYMENT-REQUIRED header, sign the payment, then retry the same POST with PAYMENT-SIGNATURE.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "$ref": "#/components/headers/PaymentRequired"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "paymentRequired": {
                    "value": {
                      "error": "payment required"
                    }
                  },
                  "verificationFailed": {
                    "value": {
                      "error": "signature expired"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Invalid or expired jobId.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "notFound": {
                    "value": {
                      "error": "job not found"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The job is not ready to send yet, or the quote expired. Poll the job status endpoint and retry when appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "notReady": {
                    "value": {
                      "error": "job not ready to send"
                    }
                  },
                  "quoteExpired": {
                    "value": {
                      "error": "quote expired"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "JobId": {
        "name": "jobId",
        "in": "path",
        "required": true,
        "description": "The x402 fax job ID returned by POST /api/x402.",
        "schema": {
          "type": "string"
        }
      },
      "PaymentSignature": {
        "name": "PAYMENT-SIGNATURE",
        "in": "header",
        "required": false,
        "description": "Base64-encoded x402 payment payload. Omit this on the first POST /send call to intentionally receive a 402 challenge.",
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "PaymentRequired": {
        "description": "Base64-encoded JSON x402 payment challenge. After base64-decoding, the JSON contains x402Version, error, resource { url, description, mimeType }, and accepts [{ scheme, network, asset, amount, payTo }].",
        "schema": {
          "type": "string"
        },
        "example": "eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJwYXltZW50IHJlcXVpcmVkIn0="
      },
      "PaymentResponse": {
        "description": "Base64-encoded JSON settlement response returned after successful payment verification or cached idempotent replay.",
        "schema": {
          "type": "string"
        },
        "example": "eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4dHgifQ=="
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable error string."
          }
        }
      },
      "PathAction": {
        "type": "object",
        "required": ["method", "path"],
        "properties": {
          "method": {
            "type": "string",
            "enum": ["GET", "POST"]
          },
          "path": {
            "type": "string"
          }
        }
      },
      "X402Descriptor": {
        "type": "object",
        "required": ["name", "version", "description", "openapi", "create", "status", "send", "flow"],
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "openapi": {
            "type": "string",
            "description": "Relative URL of the OpenAPI document."
          },
          "create": {
            "$ref": "#/components/schemas/PathAction"
          },
          "status": {
            "$ref": "#/components/schemas/PathAction"
          },
          "send": {
            "$ref": "#/components/schemas/PathAction"
          },
          "flow": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ordered steps for the expected client flow."
          }
        }
      },
      "CoverSheet": {
        "type": "object",
        "description": "Optional cover page prepended to the fax. Cover pages do not count toward page-based x402 pricing.",
        "properties": {
          "template": {
            "type": "string",
            "enum": ["standard", "irs"],
            "default": "standard",
            "description": "Cover sheet template."
          },
          "locale": {
            "type": "string",
            "enum": ["en", "de", "ja", "es", "fr", "ko", "zh-tw", "zh-cn", "sv", "nl", "it", "tr", "he"],
            "description": "Cover sheet language."
          },
          "senderName": {
            "type": "string",
            "maxLength": 200
          },
          "senderCompany": {
            "type": "string",
            "maxLength": 200
          },
          "senderPhone": {
            "type": "string",
            "maxLength": 50
          },
          "senderFax": {
            "type": "string",
            "maxLength": 50
          },
          "senderEmail": {
            "type": "string",
            "maxLength": 200
          },
          "recipientName": {
            "type": "string",
            "maxLength": 200
          },
          "recipientCompany": {
            "type": "string",
            "maxLength": 200
          },
          "recipientFax": {
            "type": "string",
            "maxLength": 50
          },
          "subject": {
            "type": "string",
            "maxLength": 200
          },
          "message": {
            "type": "string",
            "maxLength": 1000
          },
          "urgency": {
            "type": "string",
            "enum": ["normal", "urgent"],
            "default": "normal"
          },
          "confidential": {
            "type": "boolean",
            "description": "Prints a confidentiality notice on the cover page."
          },
          "forReview": {
            "type": "boolean",
            "description": "Marks the fax as for review on the cover page."
          },
          "taxpayerName": {
            "type": "string",
            "maxLength": 200,
            "description": "IRS template only."
          },
          "ssnEinLast4": {
            "type": "string",
            "maxLength": 4,
            "description": "IRS template only. Last 4 of SSN or EIN."
          },
          "taxYear": {
            "type": "string",
            "maxLength": 4,
            "description": "IRS template only, for example 2025."
          },
          "taxFormNumber": {
            "type": "string",
            "maxLength": 50,
            "description": "IRS template only, for example 1040 or W-2."
          },
          "irsNoticeNumber": {
            "type": "string",
            "maxLength": 50,
            "description": "IRS template only, for example CP2000."
          }
        }
      },
      "X402CreateRequest": {
        "type": "object",
        "required": ["faxNumber", "fileName"],
        "properties": {
          "faxNumber": {
            "type": "string",
            "description": "Destination fax number in E.164 format, for example +14155551212."
          },
          "fileName": {
            "type": "string",
            "description": "File name with extension. Used to infer MIME type when mimeType is omitted."
          },
          "mimeType": {
            "type": "string",
            "description": "Explicit MIME type, for example application/pdf."
          },
          "scheduledTime": {
            "type": "string",
            "format": "date-time",
            "description": "Optional RFC 3339 timestamp. If omitted, the fax sends immediately after payment."
          },
          "coverSheet": {
            "$ref": "#/components/schemas/CoverSheet"
          }
        }
      },
      "X402CreateResponse": {
        "type": "object",
        "required": ["jobId", "status", "uploadUrl", "statusUrl", "sendUrl"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["awaiting_upload"]
          },
          "uploadUrl": {
            "type": "string",
            "description": "Signed upload URL. PUT the raw file bytes here. The upload URL itself enforces the 100 MB file size limit."
          },
          "statusUrl": {
            "type": "string",
            "description": "Relative URL to poll job status."
          },
          "sendUrl": {
            "type": "string",
            "description": "Relative URL to trigger payment and send once the job is ready."
          }
        }
      },
      "X402Schedule": {
        "type": "object",
        "required": ["scheduledTime"],
        "properties": {
          "scheduledTime": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "X402Payment": {
        "type": "object",
        "required": ["scheme", "network", "asset", "amountAtomic", "amountDisplay", "payTo", "expiresAt"],
        "properties": {
          "scheme": {
            "type": "string",
            "description": "x402 payment scheme, for example exact."
          },
          "network": {
            "type": "string",
            "description": "CAIP-2 or x402 network identifier, for example eip155:8453 for Base."
          },
          "asset": {
            "type": "string",
            "description": "Asset contract address, typically USDC on Base."
          },
          "amountAtomic": {
            "type": "string",
            "description": "Amount in the smallest unit, as a string."
          },
          "amountDisplay": {
            "type": "string",
            "description": "Human-readable amount, for example 0.6 USDC."
          },
          "payTo": {
            "type": "string",
            "description": "Receiving wallet address."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "X402JobAwaitingUpload": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["awaiting_upload"]
          }
        }
      },
      "X402JobProcessing": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["processing"]
          },
          "pageCount": {
            "type": "integer",
            "description": "Present once page counting is available, even if the job is still finishing payment setup."
          },
          "previewUrl": {
            "type": "string",
            "description": "May appear before payment is ready if a preview PDF has already been produced."
          },
          "schedule": {
            "$ref": "#/components/schemas/X402Schedule"
          }
        }
      },
      "X402JobReadyToSend": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status", "payment", "sendUrl"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["ready_to_send"]
          },
          "pageCount": {
            "type": "integer",
            "description": "Converted fax page count."
          },
          "previewUrl": {
            "type": "string",
            "description": "Preview PDF URL, when available."
          },
          "schedule": {
            "$ref": "#/components/schemas/X402Schedule"
          },
          "payment": {
            "$ref": "#/components/schemas/X402Payment"
          },
          "sendUrl": {
            "type": "string",
            "description": "Relative URL to POST for payment and sending."
          }
        }
      },
      "X402JobSending": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["sending", "retrying"]
          },
          "pageCount": {
            "type": "integer"
          },
          "previewUrl": {
            "type": "string"
          },
          "schedule": {
            "$ref": "#/components/schemas/X402Schedule"
          }
        }
      },
      "X402JobSent": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["sent"]
          },
          "pageCount": {
            "type": "integer"
          },
          "previewUrl": {
            "type": "string"
          },
          "schedule": {
            "$ref": "#/components/schemas/X402Schedule"
          }
        }
      },
      "X402JobFailed": {
        "type": "object",
        "required": ["jobId", "faxNumber", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "faxNumber": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["failed"]
          },
          "pageCount": {
            "type": "integer"
          },
          "previewUrl": {
            "type": "string"
          },
          "schedule": {
            "$ref": "#/components/schemas/X402Schedule"
          },
          "error": {
            "type": "string",
            "description": "Failure message when available, for example payment expired."
          }
        }
      },
      "X402JobStatusResponse": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/X402JobAwaitingUpload"
          },
          {
            "$ref": "#/components/schemas/X402JobProcessing"
          },
          {
            "$ref": "#/components/schemas/X402JobReadyToSend"
          },
          {
            "$ref": "#/components/schemas/X402JobSending"
          },
          {
            "$ref": "#/components/schemas/X402JobSent"
          },
          {
            "$ref": "#/components/schemas/X402JobFailed"
          }
        ]
      },
      "X402SendAcceptedResponse": {
        "type": "object",
        "required": ["jobId", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["sending"]
          }
        }
      },
      "X402SendAlreadySentResponse": {
        "type": "object",
        "required": ["jobId", "status"],
        "properties": {
          "jobId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": ["sent"]
          }
        }
      }
    }
  }
}
