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.yamlhttps://voidly.ai/voidly-pay-openapi.jsonhttps://voidly.ai/voidly-pay-postman.jsonhttps://voidly.ai/voidly-pay-asyncapi.yaml- Hosted browser:
https://voidly.ai/pay/api-spec
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:
- Drops keys with
null/undefinedvalues - Sorts keys lexicographically
- UTF-8 encodes without whitespace (per RFC 8785 JSON Canonicalization Scheme)
- Computes SHA-256 of the canonical bytes
- 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.
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
npx openapi-typescript https://voidly.ai/voidly-pay-openapi.yaml \
--output src/types/voidly-pay.d.tsThen 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']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.techThe 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())- Open Postman →
File → Import → URL - Paste:
https://voidly.ai/voidly-pay-postman.json - The collection ships with placeholder variables:
{{baseUrl}},{{did}},{{escrowId}},{{capabilityId}},{{hireId}},{{receiptId}}. - Set
{{baseUrl}}tohttps://api.voidly.ai. - 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.
All three consume vanilla OpenAPI 3.1.
- Insomnia:
File → Import → From URL→https://voidly.ai/voidly-pay-openapi.yaml - Bruno:
Import → OpenAPI v3→ paste the URL. - Stoplight Studio:
File → Open→ paste the URL.
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.
- 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.yamland ships with Stage 1.20.
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.
Spec bugs, missing fields, generator failures, etc. — file an issue at github.com/voidly-ai/voidly-pay/issues.