---
skill: notarize
service: agora-labor
endpoint: POST /v1/notarize
price_usd: $0.10
price_atomic_usdc: 100000
payment: x402-v2
network: eip155:8453
receipt_spec: agora-receipts-v1
generated_at: 2026-08-06T11:46:42Z
---

# Skill: notarize

**OpenTimestamps notarization. POST {hash} (hex sha256) or {text} (hashed server-side; {data}/{url} also accepted) -> base64 .ots proof + calendars + verify instructions.**

| | |
| --- | --- |
| Service | `agora-labor` |
| Endpoint | `POST https://labor.virohanalife.com/v1/notarize` |
| Price | **$0.10** (100000 atomic USDC, 6 decimals) |
| Payment | x402 v2 on `eip155:8453` |
| Transport | HTTP + JSON (not JSON-RPC, not A2A) |
| Receipt | `agora-receipts-v1`, Ed25519-signed |
| Discovery | `https://labor.virohanalife.com/.well-known/x402` |

### What it does

OpenTimestamps notarization. POST {hash} (hex sha256) or {text} (hashed server-side; {data}/{url} also accepted) -> base64 .ots proof + calendars + verify instructions.

### Input

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `hash` | string | no | pattern `^[0-9a-fA-F]{64}$` |
| `text` | string | no | — |
| `data` | string | no | — |
| `url` | string | no | — |

Exactly one of these must be supplied: `hash` or `text` or `data` or `url`.

Request shape:

```json
{
  "method": "POST",
  "url": "https://labor.virohanalife.com/v1/notarize",
  "headers": {
    "content-type": "application/json",
    "X-PAYMENT": "<x402 v2 payment payload>"
  },
  "body": {
    "hash": "<hash>"
  }
}
```

### Output

| Param | Type | Required | Notes |
| --- | --- | --- | --- |
| `hash` | string | no | — |
| `sha256` | string | no | — |
| `ots_proof_b64` | string | no | — |
| `ots_file_b64` | string | no | — |
| `calendars` | array[string] | no | — |
| `verify` | string | no | — |
| `status` | string | no | — |
| `estimated_confirmation` | string | no | — |

Response schema:

```json
{
  "type": "object",
  "properties": {
    "hash": {
      "type": "string"
    },
    "sha256": {
      "type": "string"
    },
    "ots_proof_b64": {
      "type": "string"
    },
    "ots_file_b64": {
      "type": "string"
    },
    "calendars": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "verify": {
      "type": "string"
    },
    "status": {
      "type": "string"
    },
    "estimated_confirmation": {
      "type": "string"
    }
  }
}
```

### Paying for it — the x402 402-flow

The endpoint speaks **x402 v2**. Payment is a two-request loop; there is no
API key to obtain, no account to create, and no human step.

**1. Ask without paying.** Send the real request with no `X-PAYMENT` header.
You get `402 Payment Required` and a machine-readable quote:

```bash
curl -sS -X POST https://labor.virohanalife.com/v1/notarize \
  -H 'content-type: application/json' \
  -d '<your request body>'
```

```json
{
  "x402Version": 2,
  "error": "X-PAYMENT header is required",
  "resource": {
    "url": "https://labor.virohanalife.com/v1/notarize",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "100000",
      "payTo": "0xb25cc5f3475eefb6142d4a7b907a758b9652c73d",
      "asset": "USDC",
      "maxTimeoutSeconds": 60
    }
  ]
}
```

**2. Read `accepts[0]`.** It is the offer: pay `amount` ($0.10, in
atomic units of USDC, 6 decimals) to `payTo` on `eip155:8453`, within
`maxTimeoutSeconds`. `scheme: "exact"` means the exact amount, not a maximum.

**3. Pay and build the payment payload.** Settle via any x402 v2 client or
facilitator. The facilitator this service verifies against is advertised in
the 402 body under `accepts[0].extra.facilitator_url`, and again at
`https://labor.virohanalife.com/.well-known/x402`.

**4. Retry the identical request with `X-PAYMENT`.**

```bash
curl -sS -X POST https://labor.virohanalife.com/v1/notarize \
  -H 'content-type: application/json' \
  -H "X-PAYMENT: $PAYMENT_PAYLOAD" \
  -d '<the same request body>'
```

The second call returns `200` with the work product. The request body must be
byte-identical to the one you quoted against — the receipt commits to its
SHA-256.

> During the reputation-bootstrap period a free tier may be enabled for some
> routes, in which case step 1 returns `200` directly. Routes marked
> `paid=True` always require settlement regardless. The live answer is the
> `free_tier` field at `/.well-known/x402` — read it, do not assume it.

### Verifying you got what you paid for

Every settled job produces a receipt you can verify **without trusting
Virohana** — the check is offline Ed25519 arithmetic against a published key.

**Key A — the receipt-signing key** (`agora-receipts-v1`):

```
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA99UDxXbGjzF55V16DG2fQSF0FfnoojmUb5muNYT0Hzk=
-----END PUBLIC KEY-----
```

**Key B — the labor response-signing key.** Separate key, separate job: it
signs the raw response body of a paid call (`X-Agent-Signature`, and
`response_sig` in the receipt record). It is published in every receipt
returned by `https://labor.virohanalife.com/v1/receipts`:

```
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEA89J5xh6OcOGMG4Y+SIpejKbmc82xQ3VEr/9K3yk1Gpk=
-----END PUBLIC KEY-----
```

These are two different Ed25519 keys with two different jobs. Verify a
receipt's authenticity with Key A; verify that a response body is the one
the receipt commits to with Key B. Do not substitute one for the other.

**Step 1 — fetch the receipt.**

```bash
curl -sS https://labor.virohanalife.com/v1/receipts            # recent settlements
curl -sS https://labor.virohanalife.com/v1/receipts/<receipt_id>   # one receipt + verify instructions
```

**Step 2 — check the hashes commit to your job.** `input_sha256` must equal
the SHA-256 of the exact request body you sent; `output_sha256` must equal
the SHA-256 of the response body you received. If either differs, the receipt
is for a different job.

**Step 3 — verify the signature yourself.** The signature covers the
canonical JSON of the receipt *minus* the `sig` field:

```python
import json
from cryptography.hazmat.primitives.serialization import load_pem_public_key

body = dict(receipt); sig = bytes.fromhex(body.pop("sig"))
canonical = json.dumps(body, sort_keys=True, separators=(",", ":"),
                       ensure_ascii=False).encode("utf-8")
load_pem_public_key(RECEIPT_PUBKEY_PEM.encode()).verify(sig, canonical)  # raises if invalid
```

**Step 4 (optional) — second opinion.** The spine will verify a receipt for
you, which is useful as a cross-check but is *not* the trust root — step 3 is:

```bash
curl -sS -X POST https://agora.virohanalife.com/v1/receipt_verify \
  -H 'content-type: application/json' \
  -d '{"receipt": <receipt-json>}'
# -> {"valid": true|false, "reason": "..."}
```

**Step 5 — settlement is on-chain.** Paid receipts carry `tx_hash`,
`network`, `payer`, `pay_to` and `facilitator`. The USDC transfer is
independently checkable on Base regardless of anything this service says.

### Failure modes

| Status | Meaning | What to do |
| --- | --- | --- |
| `402` | No/invalid payment | Read `accepts[0]`, settle, retry with `X-PAYMENT` |
| `429` | Rate limited | Back off; per-caller limits apply |
| `400` | Input failed schema or length cap | Fix the body against the input schema above |
| `503` | Payment layer unconfigured | Fail closed by design — no work is done and nothing is charged |

