Invoance Ruby SDK
Official Ruby SDK for the Invoance compliance API — cryptographic proof infrastructure for events, documents, AI attestations, traces, and audit logs.
- Synchronous — methods return values and raise exceptions on error.
- Zero runtime dependencies — pure Ruby stdlib (
net/http,json,openssl,digest). - Offline verification — check Ed25519 signatures and recompute audit-log canonical hashes without trusting the server.
Requires Ruby >= 3.0.
Installation
Add to your Gemfile:
gem "invoance"
Then:
$ bundle install
Or install directly:
$ gem install invoance
Quick start
require "invoance"
# Reads INVOANCE_API_KEY and INVOANCE_BASE_URL from the environment.
client = Invoance::Client.new
# Or pass the key explicitly:
client = Invoance::Client.new(api_key: "inv_live_abc123")
# Ingest a compliance event
event = client.events.ingest(
event_type: "user.login",
payload: { "user_id" => "u_42" }
)
puts event["event_id"]
# Verify the API key / connectivity (never raises)
result = client.validate
raise "bad config: #{result["reason"]}" unless result["valid"]
Responses are plain Ruby Hashes with string keys matching the wire JSON
exactly — no symbolization.
Configuration
client = Invoance::Client.new(
api_key: "inv_live_...", # or ENV["INVOANCE_API_KEY"]
base_url: "https://api.invoance.com", # or ENV["INVOANCE_BASE_URL"]
api_version: "v1",
timeout: 30, # seconds
idempotency_key: nil, # default for mutating requests
extra_headers: {} # merged into every request
)
Resources
Events
client.events.ingest(event_type:, payload:, event_time: nil, trace_id: nil, idempotency_key: nil)
client.events.list(page: nil, limit: nil, date_from: nil, date_to: nil, event_type: nil)
client.events.get(event_id)
client.events.verify(event_id, payload_hash: nil, payload: nil) # provide exactly one
Documents
client.documents.anchor(document_hash:, document_ref: nil, event_type: nil,
original_bytes_b64: nil, metadata: nil, trace_id: nil,
idempotency_key: nil)
# Convenience: hash + base64 a file, then anchor.
client.documents.anchor_file(file: "./invoice.pdf", document_ref: "Invoice #1042")
# For raw bytes instead of a path:
client.documents.anchor_file(file: bytes, is_path: false, document_ref: "blob")
client.documents.list(...)
client.documents.get(event_id)
client.documents.get_original(event_id) # => raw binary String
client.documents.verify(event_id, document_hash:)
AI Attestations
client.attestations.ingest(
type: "chat.completion",
input: { "prompt" => "..." },
output: { "text" => "..." },
model_provider: "openai",
model_name: "gpt-4o",
model_version: "2026-01",
subject: { user_id: "u_1", session_id: "s_9" } # optional
)
client.attestations.list(...)
client.attestations.get(id)
client.attestations.verify(id, content_hash:)
client.attestations.get_raw(id) # => Hash
# Client-side verification (no trust in the server):
client.attestations.verify_payload(id, raw_json_string_or_hash)
result = client.attestations.verify_signature(id)
result["valid"] # => true/false
Note: for
verify_payload, pass the raw JSON string exactly as shown in the dashboard's "Raw immutable record" viewer. Key order is preserved (not sorted) because the backend hashes with struct field order.
Traces
trace = client.traces.create(label: "Batch 2026-07")
client.traces.list(status: "open")
client.traces.get(trace_id, event_page: 1, event_limit: 50)
client.traces.seal(trace_id)
client.traces.proof(trace_id) # => Hash
client.traces.proof_pdf(trace_id) # => raw binary PDF String
client.traces.delete(trace_id)
Audit Logs
client.audit.orgs.create(organization_id: "org_1", name: "Acme")
client.audit.events.ingest(
organization_id: "org_1",
action: "invoice.approved",
actor: { "type" => "user", "id" => "u_1" },
targets: [{ "type" => "invoice", "id" => "inv_9" }]
)
client.audit.events.list(organization_id: "org_1", limit: 100)
client.audit.streams.create("org_1", url: "https://siem.example/hook")
client.audit.portal_sessions.create(organization_id: "org_1", intent: "viewer")
client.audit.exports.create(organization_id: "org_1", format: "csv")
Offline verify an audit event returned by the API:
event = client.audit.events.get("evt_123")
result = Invoance::AuditVerify.verify_audit_event(event)
# => { "valid" => true, "reason" => nil, "payload_hash" => "...", "key_source" => "event" }
# Pin against the tenant's registered key for a real tamper guarantee:
Invoance::AuditVerify.verify_audit_event(event, public_key: registered_hex_key)
Error handling
Everything the SDK raises inherits from Invoance::Error:
begin
client.events.ingest(event_type: "user.login", payload: {})
rescue Invoance::QuotaExceededError => e
puts "rate limited, retry after #{e.retry_after_seconds}s"
rescue Invoance::AuthenticationError
puts "check INVOANCE_API_KEY"
rescue Invoance::Error => e
puts "Invoance error: #{e.} (status #{e.status_code})"
end
Every error exposes status_code, error_code, body,
retry_after_seconds, and request_context.
Development
$ bundle install
$ bundle exec rspec
License
MIT — see LICENSE.