API Reference
Everything the platform exposes — REST endpoints for web clients, Cap'n Proto interfaces for grain-native callers, and the authentication model that gates every request.
Overview
The Sails.to API lives at api.sails.to. Every endpoint is authenticated via Solana wallet signature and NFT role verification. There are no API keys, no OAuth tokens, no username/password flows. Your wallet is your identity. Your NFT is your authorization. The API simply verifies both and routes you to the correct grain capability.
Two access paths exist. Web clients hit the REST gateway and receive JSON responses. Grain-native callers — other grains, the operator service, automated pipelines — use Cap'n Proto RPC directly over Powerbox capabilities, with zero-copy serialization and no HTTP overhead. Both paths enforce the same authentication and compliance checks.
┌──────────────┐ REST/JSON ┌──────────────────┐
│ Web Client │ ──────────────────►│ │
└──────────────┘ │ API Gateway │
│ api.sails.to │──► Grain Capabilities
┌──────────────┐ Cap'n Proto RPC │ │
│ Grain / Svc │ ──────────────────►│ │
└──────────────┘ (FD3) └──────────────────┘
Authentication
Every request passes through the platform's four-layer authentication model before reaching any grain. The API gateway enforces the first two layers; the Sandstorm runtime enforces the remaining two:
- Wallet Signature — Ed25519 signature proving private key ownership. Included in the
X-Solana-Signatureheader alongside theX-Solana-Pubkeyheader and a timestamp-based nonce to prevent replay attacks. - NFT Verification — The gateway inspects the wallet for role-specific NFTs minted by the Melusina authority program. The required NFT tier depends on the endpoint group (see table below). No NFT, no access.
- Sandstorm Session — OS-level grain isolation, enforced by the runtime.
- Powerbox Capability — Inter-grain authority via sturdyRef tokens.
For the complete authentication model — threshold operations, session management, capability persistence — see the Authentication documentation.
Endpoint Groups
The REST gateway organizes endpoints into six groups, each gated by a specific authentication tier. Public endpoints require no wallet signature. All others require wallet signature plus the appropriate role NFT:
| Endpoint Group | Auth Requirement | Description |
|---|---|---|
/v1/offerings |
Public | List active offerings, retrieve offering details, view public cap table summaries. No authentication required — this is the storefront. |
/v1/kyc |
Wallet signature | Start a KYC verification workflow, check case status, retrieve issued credentials. Routes to the KYC/Onboarding grain for the requesting wallet. |
/v1/broker |
Broker NFT required | Place investors into offerings, list positions for secondary trading, match and settle trades, view commission reports. |
/v1/trustee |
Trustee NFT required | Authenticate CrossConversions, sign reconciliation reports, authenticate distributions, execute emergency freezes. |
/v1/investor |
Investor wallet auth | View portfolio, track distributions and yields, request CrossConversions, download tax documents, participate in governance votes. |
/v1/admin |
Master NFT required | Platform-wide management — deploy offerings, configure compliance rules, manage operator NFTs, view aggregate analytics. Requires 3-of-5 threshold for critical operations. |
Every endpoint group maps to one or more grain types behind the gateway. The /v1/broker group routes to the Broker Portal station grain. The /v1/investor group routes to the caller's individual Investor Self-Service instance grain. The gateway performs the routing — your client never addresses grains directly.
Cap'n Proto Interfaces
Behind the REST gateway, every operation is a Cap'n Proto RPC call on a grain capability. These are the core interfaces that grains expose through the Powerbox. Grain-native callers use these directly; web clients use them indirectly through the REST translation layer.
interface OfferingAPI {
subscribe @0 (investor :InvestorCredential, amount :UInt64)
-> (result :SubscriptionResult);
getCapTable @1 () -> (investors :List(InvestorPosition));
requestCrossConversion @2 (direction :ConversionDirection, amount :UInt64)
-> (request :ConversionRequest);
claimDistribution @3 (epoch :UInt32) -> (amount :UInt64);
getOfferingInfo @4 () -> (info :OfferingInfo);
}
interface KYCVerifier {
startVerification @0 (investorData :InvestorSubmission)
-> (caseId :Text);
getStatus @1 (caseId :Text) -> (status :CaseStatus);
getCredential @2 (caseId :Text) -> (credential :KYCCredential);
revokeCredential @3 (caseId :Text, reason :Text) -> (success :Bool);
}
interface TrusteeAPI {
authenticateConversion @0 (request :ConversionRequest)
-> (authenticated :Bool);
authenticateDistribution @1 (offering :Text, epoch :UInt32)
-> (authenticated :Bool);
signReconciliation @2 (report :ReconciliationReport)
-> (signature :Data);
freeze @3 (investor :Text, reason :Text) -> (success :Bool);
}
interface BrokerAPI {
placeInvestor @0 (offering :Text, investor :InvestorCredential,
amount :UInt64) -> (result :PlacementResult);
listForSecondaryTrading @1 (offering :Text, amount :UInt64,
price :UInt64) -> (listing :TradeListing);
matchTrade @2 (listing :TradeListing, buyer :InvestorCredential)
-> (settlement :TradeSettlement);
}
interface GovernanceAPI {
createProposal @0 (title :Text, description :Text,
actions :List(GovernanceAction)) -> (proposalId :Text);
vote @1 (proposalId :Text, vote :VoteChoice, weight :UInt64)
-> (success :Bool);
executeProposal @2 (proposalId :Text) -> (result :ExecutionResult);
getProposals @3 () -> (proposals :List(Proposal));
}
Each @N annotation is the Cap'n Proto method ordinal — stable across schema evolution, ensuring backward compatibility as interfaces grow. New methods are added with new ordinals; existing ordinals never change. For details on how these interfaces connect through capability grants, see the Platform Overview.
Response Format
The API supports two serialization formats, selected by the caller:
- Cap'n Proto (native) — Zero-copy deserialization. No parsing overhead. This is what grain-to-grain calls use natively over FD3. Request it from the REST gateway with
Accept: application/capnp. - JSON (web fallback) — Standard JSON over HTTP for browser clients, cURL, and any tooling that doesn't speak Cap'n Proto. This is the default when no
Acceptheader is specified.
JSON responses follow a consistent envelope:
{
"ok": true,
"data": { ... },
"meta": {
"request_id": "req_7f3a8b2c4d5e",
"grain_id": "grain_xk9p2m4n7r",
"timestamp": 1718000000
}
}
For list endpoints, the data field contains an array and meta includes pagination cursors. The platform does not use offset-based pagination — all list endpoints use opaque cursor tokens for stable iteration over changing datasets.
Error Handling
Failed requests return a structured error response with a machine-readable code, a human-readable message, and — for compliance violations — the specific rule that was violated:
{
"ok": false,
"error": {
"code": "COMPLIANCE_VIOLATION",
"message": "Transfer rejected: receiver KYC credential expired",
"details": {
"rule": "kyc_validity",
"receiver": "7xK9...",
"credential_expired_at": 1717200000
}
},
"meta": {
"request_id": "req_8b2c...",
"timestamp": 1718000000
}
}
Standard error codes:
| Code | HTTP Status | Meaning |
|---|---|---|
AUTH_SIGNATURE_INVALID |
401 | Wallet signature verification failed or nonce expired. |
AUTH_NFT_MISSING |
403 | Wallet does not hold the required role NFT for this endpoint. |
AUTH_NFT_EXPIRED |
403 | Role NFT has expired and must be reissued by the authority. |
COMPLIANCE_VIOLATION |
422 | Operation rejected by on-chain compliance rules. The details field identifies the specific rule (KYC expiry, jurisdiction mismatch, lock-up period, investor cap). |
RATE_LIMITED |
429 | Request rate exceeded for your NFT tier. Retry after the interval in the Retry-After header. |
RESOURCE_NOT_FOUND |
404 | The requested offering, investor, or resource does not exist. |
THRESHOLD_REQUIRED |
403 | Operation requires multi-signature threshold approval (e.g., 3-of-5 for Master NFT operations). |
INTERNAL_ERROR |
500 | Unexpected grain or system failure. Logged to the audit trail automatically. |
Every error — including compliance violations and authentication failures — is logged to the grain's append-only audit journal. See the Compliance Framework for details on audit trail retention and regulatory reporting.
Rate Limits
Rate limits are enforced per wallet, scaled by NFT tier. Higher-authority roles get higher limits because their operations are inherently lower-volume and higher-value. Public endpoints have the tightest limits to prevent abuse:
| NFT Tier | Requests / Minute | Burst | Notes |
|---|---|---|---|
| Public (no auth) | 30 | 10 | Offerings list and detail only. IP-based limiting. |
| Investor | 60 | 20 | Portfolio, distributions, tax documents. |
| Broker | 120 | 40 | Placement, trading, commission reporting. |
| Trustee | 120 | 40 | Authentication, reconciliation, freeze operations. |
| Platform Operator | 300 | 100 | Offering deployment, configuration, operator management. |
| Master NFT | 300 | 100 | Unrestricted admin access. Still rate-limited to prevent runaway automation. |
When a rate limit is hit, the API returns a 429 with a Retry-After header indicating the number of seconds to wait. Burst allowance permits short spikes above the per-minute rate — useful for Broker batch placements or Investor portfolio refreshes — but sustained traffic above the limit will be throttled.
Cap'n Proto RPC calls between grains are not subject to these rate limits. Rate limiting applies only to the REST gateway. Grain-to-grain calls are governed by Powerbox capability grants — if you hold the capability, you can call it. The Sandstorm runtime handles backpressure at the OS level.