Alternative Payments API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Alternative Payments API Patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The Alternative Payments API is a RESTful JSON API for B2B payments: customers, invoices, hosted payment requests, transactions, payouts, and webhooks. This skill covers OAuth2 client-credentials authentication, pagination, rate limiting, and error handling.
The WYRE integration exposes a read + safe-write surface. It deliberately does not implement direct payment creation (POST /payments), which would charge a card or bank account. Money movement is out of scope.
| Environment | Base URL |
|---|---|
| Production | https://public-api.alternativepayments.io |
| Demo | https://public-api.demo.alternativepayments.io |
Alternative Payments uses OAuth 2.0 client-credentials. Generate an API key (client_id / client_secret) in the Partner Dashboard, then exchange it for a short-lived bearer token.
Token request (HTTP Basic auth, form-encoded body):
curl -s -X POST https://public-api.alternativepayments.io/oauth/token \
-u "${AP_CLIENT_ID}:${AP_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials"Token response:
{
"access_token": "eyJhbGci...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "payments:read payments:write"
}Send the token on every API request:
Authorization: Bearer <access_token>Cache the token and refresh it shortly before expires_in elapses (a 60-second margin avoids races). On a 401, invalidate the cached token and re-authenticate once — tokens can be revoked server-side before their nominal expiry.
When connecting through the WYRE MCP Gateway, you do not send a bearer token. The gateway forwards your credentials as headers and the MCP server mints the token internally:
| Gateway header | Value |
|---|---|
X-Alternative-Payments-Client-Id | OAuth client id |
X-Alternative-Payments-Client-Secret | OAuth client secret |
X-Alternative-Payments-Environment | production or demo (optional) |
| Scope | Grants |
|---|---|
payments:read | List/get customers, invoices, transactions, payouts, webhooks |
payments:write | Create customers/invoices/payment requests/webhooks; archive; delete webhooks |
| Metric | Limit |
|---|---|
| Requests per second | 5 (per API key) |
On 429, back off and retry — respect the Retry-After header when present. A token-bucket limiter at 5/s keeps requests under the cap proactively.
async function requestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get('Retry-After') || '1', 10);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
if (res.status >= 500 && attempt < maxRetries) {
await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
continue;
}
return res;
}
throw new Error('Max retries exceeded');
}List endpoints are cursor-paginated. Pass limit and after (a cursor); responses carry the items in data plus a next_cursor / has_more indicator.
curl -s "https://public-api.alternativepayments.io/customers?limit=100&after=cursor_abc" \
-H "Authorization: Bearer ${TOKEN}"async function fetchAll(path, token) {
const items = [];
let after;
do {
const url = new URL(`https://public-api.alternativepayments.io${path}`);
url.searchParams.set('limit', '100');
if (after) url.searchParams.set('after', after);
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
const body = await res.json();
items.push(...(body.data ?? []));
after = body.has_more ? body.next_cursor : undefined;
} while (after);
return items;
}POST /payments (not exposed by this integration) requires an idempotency_key. For the create operations that ARE exposed (customers, invoices, payment requests, webhooks), supply a stable key where the API accepts one to make retries safe.
| Code | Meaning | Action |
|---|---|---|
| 200 / 201 | Success | Process response |
| 204 | Success, no body | Treat as success (e.g. archive/delete) |
| 400 / 422 | Validation error | Inspect the errors array; fix the request |
| 401 | Unauthorized | Refresh token, retry once |
| 403 | Forbidden | Check scope/permissions |
| 404 | Not found | Resource does not exist |
| 429 | Rate limited | Back off (Retry-After), retry |
| 5xx | Server error | Retry with exponential backoff |
Read the response body as text first, then JSON.parse — never call .json() and .text() on the same response (the body can only be read once).
| Resource | Method · Path | Notes |
|---|---|---|
| Customers | GET /customers · POST /customers | List / create |
GET /customers/{id} · DELETE /customers/{id} | Get / archive | |
GET /customers/{id}/users · POST /customers/{id}/users | List / add users | |
| Invoices | GET /invoices · POST /invoices | List / create with line items |
GET /invoices/{id} · DELETE /invoices/{id} | Get / archive | |
GET /invoices/{id}/payment-link | Hosted payment link | |
GET /invoices/{id}/pdf-link | Signed PDF download | |
| Payment requests | POST /payments/request · GET /payments/request/{id} | Create hosted link / get status |
| Transactions | GET /payments · GET /payments/{id} | List / get (read-only) |
| Payouts | GET /payouts · GET /payouts/{id} | List / get |
GET /payouts/{id}/transactions | Transactions in a payout | |
| Webhooks | GET /webhooks · POST /webhooks | List / subscribe |
DELETE /webhooks/{id} · GET /webhooks/events · POST /webhooks/retry | Unsubscribe / events / retry |
Excluded by design:POST /payments(direct charge), the Web-SDKPOST /v1/checkout-auth/init, and the/address/*Google Places utilities.
has_more / next_cursor.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.