{
  "openapi": "3.1.0",
  "info": {
    "title": "Hashlock-Algo — Proof of Existence on Algorand",
    "description": "Cryptographic timestamping service. Proves that a document existed at a specific moment in time, recorded immutably on the Algorand blockchain.\n\nTwo payment methods available:\n- **POST /timestamp** — Algorand USDC (via x402-avm, no EVM wallet needed)\n- **POST /timestamp-base** — Base EVM USDC (EIP-3009 / gasless, no Algorand wallet needed)\n\nBoth methods cost 0.005 USDC and produce the same result: a SHA-256 hash recorded on Algorand with a PDF certificate.\n\nVerification (`GET /verify/:hash`) is always free.",
    "version": "1.3.0",
    "contact": {
      "url": "https://hashlock.pronodealgo.xyz/"
    }
  },
  "servers": [
    {
      "url": "https://hashlock.pronodealgo.xyz/api",
      "description": "Testnet (Algorand Testnet + Base Sepolia). Mainnet temporarily suspended."
    }
  ],
  "paths": {
    "/timestamp": {
      "post": {
        "summary": "Timestamp a document hash — Algorand USDC payment",
        "description": "Records a SHA-256 hash on the Algorand blockchain. Payment: 0.005 USDC via Algorand USDC ASA (x402-avm protocol).\n\nFlow:\n1. POST without payment → server replies **402** with `PAYMENT-REQUIRED` header (base64-JSON containing payment instructions)\n2. Build and sign an Algorand USDC transfer using `@x402-avm/avm`\n3. Retry POST with `PAYMENT-SIGNATURE` header (base64-JSON)\n\nThe server then records the hash on Algorand and returns the transaction ID.\n\nRequired: Algorand wallet with USDC (opted in to ASA 31566704 on mainnet / 10458941 on testnet).",
        "operationId": "timestampDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hash"
                ],
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 hex hash of the document (64 lowercase hex chars)",
                    "example": "3f1c8a2b9e4d7f6a0c5b2e8d1a4f7c3e9b5d2a7f0e6c1d8b3a5f9e2c7d4b6a1f"
                  },
                  "filename": {
                    "type": "string",
                    "description": "Original file name (stored for reference, never the file itself)",
                    "example": "contract_v2.pdf"
                  },
                  "filesize": {
                    "type": "number",
                    "description": "File size in bytes",
                    "example": 42318
                  },
                  "ownerAddress": {
                    "type": "string",
                    "description": "Algorand address of the document owner (58-char base32)",
                    "example": "4YE3PSCYV4WNEX7OVJMBOJXEU256NMWR5K5GPC6AUVIWP7UJP7CJZ6KVX4"
                  },
                  "description": {
                    "type": "string",
                    "description": "Short label. Max 100 chars. a-z, A-Z, 0-9, spaces only. Stored on-chain only when publicMetadata is true; otherwise kept server-side.",
                    "example": "Contract v2 signed 2026"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "web",
                      "agent"
                    ],
                    "default": "web",
                    "description": "Origin of the request. Use 'agent' for AI agent / automated calls."
                  },
                  "ownerSignature": {
                    "type": "string",
                    "description": "Base64 ed25519 signature (64 bytes) of 'hashlock-algo-v1:timestamp:<hash-lowercase>' signed by ownerAddress. Proves ownership; the server writes a salted commitment on-chain (never the address) and returns ownerSalt once. Algorand addresses only."
                  },
                  "publicMetadata": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, filename/filesize/description and ownerAddress are embedded in the PUBLIC, PERMANENT transaction note. Default: only the hash and timestamp go on-chain; metadata stays server-side (erasable)."
                  },
                  "blinded": {
                    "type": "boolean",
                    "default": false,
                    "description": "Declare that the hash is SHA256(file + secret) computed client-side. The secret never reaches the server."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Timestamp recorded successfully on Algorand",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "hash": {
                      "type": "string"
                    },
                    "txId": {
                      "type": "string",
                      "example": "W7M6E7BOBTC6HIVGXHUKDBBEBP3Y6CBTSRE5J4G3XQGZ3APPNRMQ"
                    },
                    "block": {
                      "type": "number",
                      "example": 62796339
                    },
                    "network": {
                      "type": "string",
                      "example": "algorand-mainnet"
                    },
                    "explorerUrl": {
                      "type": "string",
                      "example": "https://lora.algokit.io/mainnet/transaction/W7M6E7BOBTC6HIVGXHUKDBBEBP3Y6CBTSRE5J4G3XQGZ3APPNRMQ"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "certificate": {
                      "type": "string",
                      "description": "PDF certificate encoded in base64. Decode and write to a .pdf file."
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment required (x402 protocol). Parse the PAYMENT-REQUIRED header, sign the Algorand USDC transfer, and retry with the PAYMENT-SIGNATURE header.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "Base64-encoded JSON with payment instructions (scheme, network, amount, payTo, asset)",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "Hash already timestamped. The existing record and certificate are returned.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "Hash already timestamped"
                    },
                    "existing": {
                      "type": "object",
                      "properties": {
                        "txId": {
                          "type": "string"
                        },
                        "block": {
                          "type": "number"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "explorerUrl": {
                          "type": "string"
                        }
                      }
                    },
                    "certificate": {
                      "type": "string",
                      "description": "PDF certificate encoded in base64"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing or malformed hash, invalid description)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/timestamp-base": {
      "post": {
        "summary": "Timestamp a document hash — Base EVM USDC payment",
        "description": "Records a SHA-256 hash on the Algorand blockchain, paid with USDC on Base EVM (EIP-3009 TransferWithAuthorization, gasless). No Algorand wallet required.\n\nFlow:\n1. POST without payment → server replies **402** with `PAYMENT-REQUIRED` header (base64-JSON with `accepts[0]` containing `amount`, `payTo`, `asset`, `network`, `extra`, `maxTimeoutSeconds`)\n2. Sign an EIP-3009 `TransferWithAuthorization` off-chain with `viem` (no gas needed)\n3. Build the payment payload and retry POST with `PAYMENT-SIGNATURE` header (base64-JSON)\n\nThe `x402` facilitator (xpay.sh) settles the USDC transfer on Base. The server then records the hash on Algorand.\n\nRequired: EVM wallet with USDC on Base Mainnet (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) or Base Sepolia (`0x036CbD53842c5426634e7929541eC2318f3dCF7e`).",
        "operationId": "timestampDocumentBase",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "hash"
                ],
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 hex hash of the document (64 lowercase hex chars)",
                    "example": "3f1c8a2b9e4d7f6a0c5b2e8d1a4f7c3e9b5d2a7f0e6c1d8b3a5f9e2c7d4b6a1f"
                  },
                  "filename": {
                    "type": "string",
                    "description": "Original file name",
                    "example": "contract_v2.pdf"
                  },
                  "filesize": {
                    "type": "number",
                    "description": "File size in bytes",
                    "example": 42318
                  },
                  "ownerAddress": {
                    "type": "string",
                    "description": "EVM address (0x + 40 hex chars) or Algorand address of the document owner",
                    "example": "0x503D4b87df5892e51351cFbA4c07a530664dC9f0"
                  },
                  "description": {
                    "type": "string",
                    "description": "Short label. Max 100 chars. a-z, A-Z, 0-9, spaces only. Stored on-chain only when publicMetadata is true; otherwise kept server-side.",
                    "example": "Contract v2 signed 2026"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "web",
                      "agent"
                    ],
                    "default": "web",
                    "description": "Origin of the request. Use 'agent' for AI agent / automated calls."
                  },
                  "ownerSignature": {
                    "type": "string",
                    "description": "Base64 ed25519 signature (64 bytes) of 'hashlock-algo-v1:timestamp:<hash-lowercase>' signed by ownerAddress. Proves ownership; the server writes a salted commitment on-chain (never the address) and returns ownerSalt once. Algorand addresses only."
                  },
                  "publicMetadata": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, filename/filesize/description and ownerAddress are embedded in the PUBLIC, PERMANENT transaction note. Default: only the hash and timestamp go on-chain; metadata stays server-side (erasable)."
                  },
                  "blinded": {
                    "type": "boolean",
                    "default": false,
                    "description": "Declare that the hash is SHA256(file + secret) computed client-side. The secret never reaches the server."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Timestamp recorded successfully on Algorand (paid via Base EVM)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "hash": {
                      "type": "string"
                    },
                    "txId": {
                      "type": "string",
                      "description": "Algorand transaction ID",
                      "example": "W7M6E7BOBTC6HIVGXHUKDBBEBP3Y6CBTSRE5J4G3XQGZ3APPNRMQ"
                    },
                    "block": {
                      "type": "number",
                      "example": 62796339
                    },
                    "network": {
                      "type": "string",
                      "example": "algorand-mainnet"
                    },
                    "explorerUrl": {
                      "type": "string",
                      "description": "Algorand explorer URL for the inscription transaction"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "certificate": {
                      "type": "string",
                      "description": "PDF certificate encoded in base64"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "Payment required (x402 EVM protocol). Parse PAYMENT-REQUIRED, sign EIP-3009 TransferWithAuthorization with viem, retry with PAYMENT-SIGNATURE.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "Base64-encoded JSON. Key field: `accepts[0]` with `amount` (5000 = 0.005 USDC), `payTo`, `asset` (USDC contract), `network` (eip155:8453 or eip155:84532), `extra` (name/version for EIP-712 domain), `maxTimeoutSeconds`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "409": {
            "description": "Hash already timestamped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "existing": {
                      "type": "object",
                      "properties": {
                        "txId": {
                          "type": "string"
                        },
                        "block": {
                          "type": "number"
                        },
                        "createdAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "explorerUrl": {
                          "type": "string"
                        }
                      }
                    },
                    "certificate": {
                      "type": "string",
                      "description": "PDF certificate encoded in base64"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/verify/{hash}": {
      "get": {
        "summary": "Verify a document hash (free)",
        "description": "Public and free. Checks if a SHA-256 hash has been timestamped on Algorand.",
        "operationId": "verifyHash",
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "SHA-256 hex hash of the document (64 lowercase hex chars)",
            "example": "3f1c8a2b9e4d7f6a0c5b2e8d1a4f7c3e9b5d2a7f0e6c1d8b3a5f9e2c7d4b6a1f"
          }
        ],
        "responses": {
          "200": {
            "description": "Verification result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "verified": {
                      "type": "boolean"
                    },
                    "hash": {
                      "type": "string"
                    },
                    "txId": {
                      "type": "string"
                    },
                    "block": {
                      "type": "number"
                    },
                    "createdAt": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "explorerUrl": {
                      "type": "string"
                    },
                    "filename": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/history/{address}": {
      "get": {
        "summary": "Get timestamp history for an address (free)",
        "description": "Returns the list of timestamps associated with a given owner address (Algorand or EVM).",
        "operationId": "getHistory",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Algorand address (58-char base32) or EVM address (0x + 40 hex chars)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "number",
              "default": 20,
              "maximum": 200
            },
            "description": "Max records to return"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "number",
              "default": 0
            },
            "description": "Pagination offset"
          }
        ],
        "responses": {
          "200": {
            "description": "History returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "address": {
                      "type": "string"
                    },
                    "total": {
                      "type": "number"
                    },
                    "count": {
                      "type": "number"
                    },
                    "limit": {
                      "type": "number"
                    },
                    "offset": {
                      "type": "number"
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "hash": {
                            "type": "string"
                          },
                          "txId": {
                            "type": "string"
                          },
                          "block": {
                            "type": "number"
                          },
                          "network": {
                            "type": "string"
                          },
                          "filename": {
                            "type": "string"
                          },
                          "filesize": {
                            "type": "number"
                          },
                          "description": {
                            "type": "string"
                          },
                          "createdAt": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "explorerUrl": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/stats": {
      "get": {
        "summary": "Global service statistics (free)",
        "description": "Returns usage statistics: total timestamps, daily breakdown, network info, wallet address.",
        "operationId": "getStats",
        "responses": {
          "200": {
            "description": "Statistics returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "number"
                    },
                    "today": {
                      "type": "number"
                    },
                    "wallets": {
                      "type": "number"
                    },
                    "totalWeb": {
                      "type": "number"
                    },
                    "totalAgent": {
                      "type": "number"
                    },
                    "totalAgentAlgo": {
                      "type": "number"
                    },
                    "totalAgentEvm": {
                      "type": "number"
                    },
                    "network": {
                      "type": "string",
                      "example": "algorand-mainnet"
                    },
                    "walletAddress": {
                      "type": "string"
                    },
                    "usdcAssetId": {
                      "type": "number"
                    },
                    "explorerUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/history-challenge": {
      "post": {
        "summary": "Owner unlock, step 1: get a single-use challenge nonce (free)",
        "description": "The public history hides filenames. The owner of an address can unlock them by proving key possession. This endpoint returns a single-use nonce (expires in 5 minutes) to embed in the proof. Nothing is paid, nothing is broadcast.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string",
                    "description": "Algorand address (58 chars)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Challenge issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "nonce": {
                      "type": "string"
                    },
                    "expiresIn": {
                      "type": "number",
                      "description": "Seconds before the nonce expires"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid address"
          }
        }
      }
    },
    "/history-auth": {
      "post": {
        "summary": "Owner unlock, step 2: exchange a proof of key possession for the private history (free)",
        "description": "Accepts either proof: mode 'txn' (a signed 0-ALGO self-payment that can never be broadcast: lastValid=1000, note 'hashlock-algo-v1:history:<nonce>') or mode 'siwa' (ARC-60/CAIP-122: ed25519 signature over sha256(canonicalJSON) || sha256(authenticatorData), authenticatorData = sha256(domain)). Returns a 24 h session token ('jeton') reusable without re-signing, plus the records with filename, filesize, description and paymentTxId. Tokens die on server restart; re-authenticate.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "address"
                ],
                "properties": {
                  "address": {
                    "type": "string"
                  },
                  "jeton": {
                    "type": "string",
                    "description": "Session token from a previous call - skips the proof"
                  },
                  "preuve": {
                    "type": "object",
                    "description": "Proof of key possession",
                    "properties": {
                      "mode": {
                        "type": "string",
                        "enum": [
                          "txn",
                          "siwa"
                        ]
                      },
                      "stxn": {
                        "type": "string",
                        "description": "mode txn: base64 signed unbroadcastable transaction"
                      },
                      "data": {
                        "type": "string",
                        "description": "mode siwa: base64 canonical SIWA JSON"
                      },
                      "signature": {
                        "type": "string",
                        "description": "mode siwa: base64 ed25519 signature"
                      },
                      "authenticatorData": {
                        "type": "string",
                        "description": "mode siwa: base64 sha256(domain)"
                      }
                    }
                  },
                  "limit": {
                    "type": "number",
                    "default": 20,
                    "maximum": 200
                  },
                  "offset": {
                    "type": "number",
                    "default": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Private history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "proprietaire": {
                      "type": "boolean"
                    },
                    "jeton": {
                      "type": "string"
                    },
                    "expiresAt": {
                      "type": "number"
                    },
                    "total": {
                      "type": "number"
                    },
                    "records": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "hash": {
                            "type": "string"
                          },
                          "txId": {
                            "type": "string"
                          },
                          "filename": {
                            "type": "string",
                            "nullable": true
                          },
                          "filesize": {
                            "type": "number",
                            "nullable": true
                          },
                          "description": {
                            "type": "string",
                            "nullable": true
                          },
                          "paymentTxId": {
                            "type": "string",
                            "nullable": true
                          },
                          "createdAt": {
                            "type": "string"
                          },
                          "explorerUrl": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Proof refused (raison field explains why)"
          },
          "400": {
            "description": "Invalid address"
          }
        }
      }
    }
  }
}