Quickstart
Call any service without API keys. No setup. Zero credentials.
Call a service in one line
import { call } from "@private.me/xlink";
const result = await call("payments:createCharge", {
amount: 100,
currency: "USD"
});
That's it. No API keys. No tokens. No setup steps.
Install
npm install @private.me/xlink
Purchase & Deploy an ACI
Purchase any ACI programmatically using the Private.Me purchase API. All purchases support idempotency keys to prevent duplicate charges and include structured error handling.
Basic Purchase (TypeScript)
import { randomUUID } from 'crypto';
const response = await fetch('https://private.me/api/purchase', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
'X-Client-Type': 'ai-agent' // Optional: 60 req/min vs 10 req/min
},
body: JSON.stringify({
product: 'xlink',
tier: 'basic', // 'basic' | 'middle' | 'enterprise'
connection_id: 'my-production-connection'
})
});
const data = await response.json();
if (response.ok) {
console.log('Purchase successful:', data.subscription_id);
} else {
// Parse RFC 7807 error
console.error(data.title);
if (data.fields) {
// Field-level validation errors
Object.entries(data.fields).forEach(([field, error]) => {
console.error(`${field}: ${error}`);
});
}
}
Retry Logic for Rate Limits (TypeScript)
async function purchaseWithRetry(params, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await fetch('https://private.me/api/purchase', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
'X-Client-Type': 'ai-agent'
},
body: JSON.stringify(params)
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
console.log(`Rate limited. Retrying in ${retryAfter}s...`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response.json();
}
throw new Error('Max retries exceeded');
}
Python Purchase with Error Handling
import requests
import uuid
import time
def purchase_aci(product: str, tier: str, connection_id: str, is_ai_agent: bool = False):
headers = {
'Content-Type': 'application/json',
'Idempotency-Key': str(uuid.uuid4())
}
if is_ai_agent:
headers['X-Client-Type'] = 'ai-agent' # 60 req/min
response = requests.post('https://private.me/api/purchase',
headers=headers,
json={
'product': product,
'tier': tier,
'connection_id': connection_id
}
)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f'Rate limited. Waiting {retry_after}s...')
time.sleep(retry_after)
return purchase_aci(product, tier, connection_id, is_ai_agent)
data = response.json()
if response.status_code == 200:
return {
'success': True,
'subscription_id': data['subscription_id'],
'connection_id': data['connection_id']
}
else:
return {
'success': False,
'error': data.get('title', 'Unknown error'),
'fields': data.get('fields', {})
}
Go Purchase with Structured Errors
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/google/uuid"
)
type PurchaseRequest struct {
Product string `json:"product"`
Tier string `json:"tier"`
ConnectionID string `json:"connection_id"`
}
type ErrorResponse struct {
Type string `json:"type"`
Title string `json:"title"`
Detail string `json:"detail"`
Fields map[string]string `json:"fields,omitempty"`
}
func purchaseACI(req PurchaseRequest, isAIAgent bool) error {
body, _ := json.Marshal(req)
httpReq, _ := http.NewRequest("POST", "https://private.me/api/purchase", bytes.NewReader(body))
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Idempotency-Key", uuid.New().String())
if isAIAgent {
httpReq.Header.Set("X-Client-Type", "ai-agent")
}
resp, err := http.DefaultClient.Do(httpReq)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 429 {
retryAfter := resp.Header.Get("Retry-After")
if retryAfter == "" {
retryAfter = "60"
}
duration, _ := time.ParseDuration(retryAfter + "s")
time.Sleep(duration)
return purchaseACI(req, isAIAgent)
}
if resp.StatusCode != 200 {
var errResp ErrorResponse
json.NewDecoder(resp.Body).Decode(&errResp)
if len(errResp.Fields) > 0 {
for field, msg := range errResp.Fields {
fmt.Printf("Field %s: %s\n", field, msg)
}
}
return fmt.Errorf("%s: %s", errResp.Title, errResp.Detail)
}
return nil
}
Idempotency: The Idempotency-Key header prevents duplicate purchases if the same request is sent multiple times. Use a unique UUID for each purchase attempt.
Rate Limits: AI agents get 60 requests/minute with X-Client-Type: ai-agent header. Human-initiated requests are limited to 10 requests/minute. The API returns 429 Too Many Requests with a Retry-After header when limits are exceeded.
Error Handling: All errors follow RFC 7807 Problem Details format with type, title, detail, and optional fields object for field-level validation errors.
What you just did (optional)
You connected to the payments service, authenticated both sides automatically, and sent a signed request.
No credentials were created or stored.
Handle the response
const result = await call("crm:searchContacts", { q: "Alice" });
if (result.ok) {
console.log("Found:", result.value);
} else {
console.error(result.error.code, result.error.message);
// Field-level validation errors (if present)
if (result.error.fields) {
Object.entries(result.error.fields).forEach(([field, error]) => {
console.error(` ${field}: ${error}`);
});
}
}
The Result pattern gives you safe, structured error handling with field-level validation details when applicable.
Or use unwrap for simple cases
// Throws on error, returns value directly
const contacts = await call("crm:searchContacts", {
q: "Alice"
}).unwrap();
If you need more control
Reuse a connection
import { connect } from "@private.me/xlink";
const payments = await connect("payments");
await payments.send({ action: "createCharge", amount: 100 });
await payments.send({ action: "refund", amount: 50 });
Persistent connections reduce handshake overhead for multiple calls.
Connect to your own service
Use an invite (one-time approval), then call it the same way:
await call("billing:createInvoice", { amount: 99.5 });
That's the whole model
Call services by name. No API keys. Works like a normal request.
How it works (optional)
Expand to learn more
Each service has a cryptographic identity. Requests are signed per-call (no reusable tokens). Both sides verify each other.
No long-lived credentials means nothing to rotate or leak.
Upgrade security (optional)
await call("vault:getPII", { id: "123" }, { mode: "secure" });
simple (default): Fastest, replaces API keys
secure: Multi-path with stronger guarantees
Migrate from existing APIs
import { DualModeAdapter } from "@private.me/xlink";
const api = new DualModeAdapter({
xlink: true,
fallback: {
url: "https://api.example.com",
apiKey: process.env.API_KEY
}
});
await api.call("createCharge", { amount: 100 });
Tries Private.Me first. Falls back automatically. Remove API keys when ready.
Done
You now have keyless authentication, encrypted requests, and zero-setup connections.
Next: See Concepts to understand how it works, or White Papers to explore the 207 ACIs available.