Skip to content

Latest commit

 

History

History
215 lines (163 loc) · 7.46 KB

File metadata and controls

215 lines (163 loc) · 7.46 KB

Voidly Pay — API specifications

This directory holds the canonical machine-readable specifications for Voidly Pay. Use them to generate typed clients, populate Postman, build mocks, or validate your own server implementation.

File Purpose
openapi.yaml OpenAPI 3.1 — 34 HTTPS endpoints under /v1/pay/*
asyncapi.yaml AsyncAPI 2.6 — roadmap SSE + webhook events (no streams in Stage 1.16)

The same spec is mirrored at:

  • https://voidly.ai/voidly-pay-openapi.yaml
  • https://voidly.ai/voidly-pay-openapi.json
  • https://voidly.ai/voidly-pay-postman.json
  • https://voidly.ai/voidly-pay-asyncapi.yaml
  • Hosted browser: https://voidly.ai/pay/api-spec

Authentication model

Voidly Pay uses Ed25519-signed canonical JSON envelopes. There are no API keys, bearer tokens, or auth headers. Every write request carries an envelope object plus a signature string in the request body.

The relay:

  1. Drops keys with null / undefined values
  2. Sorts keys lexicographically
  3. UTF-8 encodes without whitespace (per RFC 8785 JSON Canonicalization Scheme)
  4. Computes SHA-256 of the canonical bytes
  5. Verifies the base64 signature against the sender's registered public key

Reads (GET) are public — no auth required.

In OpenAPI terms this is modelled as securitySchemes.SignedEnvelope (an apiKey-style stub with an x-auth-model: ed25519-body-signature extension) so generators can mark write operations as requiring auth without trying to insert a non-existent header.

For admin operations, the same model uses pay_admin_keys for key lookup and adds an action_nonce + valid_until_ts (≤10 min window) for replay protection — modelled as securitySchemes.AdminSignedEnvelope.

The TypeScript SDK (@voidly/agent-sdk) and the @voidly/mcp-server package handle canonicalization and signing for you.

Quick reference

Base URL:         https://api.voidly.ai
Spec format:      OpenAPI 3.1 (also valid 3.0.x with `nullable` rewrite)
DID format:       did:voidly:<base58(pubkey[0..16])>
Amount unit:      micro-credits (1 credit = 1,000,000 micro)
Default caps:     1,000 cr/day, 100 cr/transfer (admin-overridable)
Envelope window:  ≤60 minutes (issued_at → expires_at)
Clock skew:       ±30 seconds

Code generation

TypeScript types (recommended)

npx openapi-typescript https://voidly.ai/voidly-pay-openapi.yaml \
  --output src/types/voidly-pay.d.ts

Then in your client:

import type { paths } from './types/voidly-pay'

type Wallet =
  paths['/v1/pay/wallet/{did}']['get']['responses']['200']['content']['application/json']

type TransferReceipt =
  paths['/v1/pay/transfer']['post']['responses']['200']['content']['application/json']

Full client (openapi-generator)

Generate clients in 30+ languages:

# Python
npx @openapitools/openapi-generator-cli generate \
  -i https://voidly.ai/voidly-pay-openapi.yaml \
  -g python -o ./voidly_pay_client

# Go
npx @openapitools/openapi-generator-cli generate \
  -i https://voidly.ai/voidly-pay-openapi.yaml \
  -g go -o ./voidly-pay-go \
  --additional-properties=packageName=voidlypay

# Rust
npx @openapitools/openapi-generator-cli generate \
  -i https://voidly.ai/voidly-pay-openapi.yaml \
  -g rust -o ./voidly-pay-rs

# Java, Kotlin, Swift, C#, PHP, Ruby, etc. — see openapi-generator.tech

The generated clients ship with the full schema, but do not include canonical JSON signing. Wrap the generated transfer / escrow_open / hire operations in your own helper that builds the envelope, signs it, and submits.

A complete Python signing helper:

from nacl.signing import SigningKey
import json
import base64
import requests

def canonicalize(obj):
    """RFC 8785-lite: sorted keys, no whitespace, drop null/undefined."""
    if isinstance(obj, dict):
        items = sorted((k, v) for k, v in obj.items() if v is not None)
        return "{" + ",".join(json.dumps(k) + ":" + canonicalize(v) for k, v in items) + "}"
    if isinstance(obj, list):
        return "[" + ",".join(canonicalize(v) for v in obj) + "]"
    return json.dumps(obj, separators=(",", ":"))

def sign_envelope(envelope: dict, secret_key: bytes) -> str:
    sk = SigningKey(secret_key[:32])
    sig = sk.sign(canonicalize(envelope).encode("utf-8")).signature
    return base64.b64encode(sig).decode()

# Use it:
envelope = {
    "schema": "voidly-credit-transfer/v1",
    "from_did": "did:voidly:Eg8...",
    "to_did":   "did:voidly:AsA...",
    "amount_micro": 1000,
    "nonce": "tx-001",
    "issued_at":  "2026-04-25T20:00:00Z",
    "expires_at": "2026-04-25T20:30:00Z",
}
signature = sign_envelope(envelope, my_secret_key)
r = requests.post(
    "https://api.voidly.ai/v1/pay/transfer",
    json={"envelope": envelope, "signature": signature},
)
print(r.json())

Postman quickstart

  1. Open Postman → File → Import → URL
  2. Paste: https://voidly.ai/voidly-pay-postman.json
  3. The collection ships with placeholder variables: {{baseUrl}}, {{did}}, {{escrowId}}, {{capabilityId}}, {{hireId}}, {{receiptId}}.
  4. Set {{baseUrl}} to https://api.voidly.ai.
  5. For read endpoints, just hit Send. For writes, you need to sign the envelope locally — Postman does not have native Ed25519, so use the SDK to produce { envelope, signature } and paste into the body.

Insomnia / Bruno / Stoplight

All three consume vanilla OpenAPI 3.1.

  • Insomnia: File → Import → From URLhttps://voidly.ai/voidly-pay-openapi.yaml
  • Bruno: Import → OpenAPI v3 → paste the URL.
  • Stoplight Studio: File → Open → paste the URL.

Validating the spec

The YAML passes both swagger-cli and redocly lint with zero errors:

npx @apidevtools/swagger-cli validate openapi.yaml
# → openapi.yaml is valid

npx @redocly/cli lint --skip-rule no-invalid-media-type-examples openapi.yaml
# → Woohoo! Your API description is valid.

The skipped rule (no-invalid-media-type-examples) is a known Redocly quirk where examples that include a schema discriminator field — required by Voidly's response envelope convention — are flagged as "unevaluated properties". The schema itself does declare schema; the rule's structural matcher misses our Error.schema property.

What's not in the spec

  • Canonicalization implementation — see the envelope source.
  • Signing — runtime — use @voidly/agent-sdk (TypeScript) or the Python helper above.
  • Streaming — Stage 1.16 has no SSE / WebSocket / push endpoints. The roadmap is documented in asyncapi.yaml and ships with Stage 1.20.

Versioning

Spec version follows the deployed Voidly Pay stage:

Spec version Stage What changed
1.16.0 1.16 — production Initial public spec, 34 endpoints
1.16.1 1.16.1 — hardened Default 4xx/5xx responses, Error schema, OAS 3.1 nullable fix
1.16.2 1.16.2 — auth hardened Per-operation security declarations, SignedEnvelope + AdminSignedEnvelope security schemes

Breaking changes will bump the major version (2.x.x) and ship behind /v2/pay/* URLs alongside /v1/pay/* until v1 is retired with at least 6 months of deprecation notice.

Issues / contributions

Spec bugs, missing fields, generator failures, etc. — file an issue at github.com/voidly-ai/voidly-pay/issues.