Corporate Payout
Partner payout request. CREDITED indicates successful funding. A direct allocation is stored separately.
GET /api/v1/me/payoutsImplementation-oriented technical contract. Open each endpoint to inspect IN/OUT fields, types, required conditions, constraints, success responses, errors and security boundaries. Includes card PIN setup and issuer card transactions, clearly separated from the Merchant HMAC API.
These are merchant backend APIs, never browser-side secret-bearing APIs. Verify the logged-in learner on your backend; V-REC and SAKURA both enforce membership and merchant binding.
Production prerequisites: Requires an isolated merchant-to-SAKURA connection, operator-granted credential scopes and explicit Connected Apps reauthorization. Unisolated shared connections fail closed with 409.
| Method | Endpoint | HMAC scope | Purpose |
|---|---|---|---|
GET | /api/merchant/v1/learning/programs | learning.read | List up to 100 published courses belonging to this merchant |
GET | /api/merchant/v1/learning/programs/{id} | learning.read | Course details and enrollment conditions (no secret URLs or quiz answers) |
POST | /api/merchant/v1/learning/programs/{id}/enrollments | learning.enroll.write | Enroll consented member in free course only |
GET | /api/merchant/v1/learning/programs/{id}/sessions | learning.read | Get lesson schedule and completion rules |
GET | /api/merchant/v1/learning/users/{id}/schedule | learning.read | Read member course schedule |
GET | /api/merchant/v1/learning/users/{id}/progress | learning.read | Read member attendance progress and Google Calendar add URLs |
GET | /api/merchant/v1/learning/rewards | learning.read | Read recent custom token learning rewards |
POST | /api/merchant/v1/learning/sessions/{id}/complete | learning.rewards.write | Submit external lesson completion evidence |
POST | /api/merchant/v1/learning/sessions/{id}/rewards | learning.rewards.write | Retry configured reward for completed lesson |
POST | /api/merchant/v1/learning/users/{id}/calendar-sync | learning.calendar.write | Sync enrolled lessons to approved Google Calendar |
GET | /api/merchant/v1/token-pool | learning.token_pool.read | Read dedicated ERC-20 pool and all-time ledger statistics |
GET | /api/merchant/v1/token-pool/transfers | learning.token_pool.read | List pool transfers, statistics and cursor pagination |
GET | /api/merchant/v1/token-pool/transfers/{id} | learning.token_pool.read | Read a submitted transfer by request ID |
POST | /api/merchant/v1/token-pool/transfers | learning.token_pool.write | Request gasless ERC-20 transfer from dedicated merchant pool |
See the expandable “Merchant Learning & Custom Token Pool API” cards below for all 14 endpoint inputs, outputs and errors. IN / OUT reference
POST /api/merchant/v1/learning/programs/331/enrollments
Content-Type: application/json
{"user_id":"usr_member_1234","learner_consent":true,"consent_reference":"consent-record-20261031-01"}
Both systems validate merchant membership. Explicit permission is required. Paid courses return 422 and must be purchased by the member.
POST /api/merchant/v1/learning/sessions/371/complete
Content-Type: application/json
{"user_id":"usr_member_1234","idempotency_key":"course-371-user-1234","evidence_ref":"provider-attendance-371-1234","duration_seconds":3600}
The lesson must have ended. SAKURA evaluates attendance, quiz and survey rules. HTTP 202 can mean pending; it is not token or blockchain confirmation. Reward amount is fixed by the configured lesson.
GET progress supplies google_calendar_add_url for a user-clicked Google save screen. Automatic sync requires the user’s prior Google OAuth approval; missing authorization returns 409, partial failure may return 207.
GET /api/merchant/v1/learning/users/usr_member_1234/progress
POST /api/merchant/v1/learning/users/usr_member_1234/calendar-sync
{"program_id":331}
POST /api/merchant/v1/token-pool/transfers
{"merchant_code":"mrc_your_merchant_code","user_id":"usr_member_1234","request_id":"merchant-token-20261031-00001","amount":"10.500000"}
GET /api/merchant/v1/token-pool/transfers/merchant-token-20261031-00001
Requires ACTIVE/FULL membership, matching merchant, active ERC recipient wallet, dedicated custom-token backing and approved relayer. This is neither the USDT collateral pool nor a lesson reward. submitted is not confirmed; reconcile_required must not be automatically rebroadcast. Payout stays disabled until the relayer is verified.
External integration workflow · OpenAPI YAML · Merchant dashboard (login required)
Requires the explicit credential scopes kyc.preload and kyc.link.issue, which the Merchant grants in Merchant Portal → Merchant API after acknowledging its consent obligations (never included in a legacy "All" key). Standard users.write / kyc.read do not enable this flow. Completing the browser flow is not provider approval: wait until SAKURA/MatchMove reports APPROVED.
GET /api/merchant/v1/profile-options
POST /api/merchant/v1/users {"onboarding_mode":"ekyc_only", ...}
GET /api/merchant/v1/users/{id}/kyc/schema
POST /api/merchant/v1/users/{id}/kyc/preload
Idempotency-Key: unique-draft-request-id
JSON: {"fields": {...}, "documents": {...}, "consent": {...}}
GET /api/merchant/v1/users/{id}/kyc/preload-status
POST /api/merchant/v1/users/{id}/kyc/preload-reconcile # only authorized correction
POST /api/merchant/v1/users/{id}/kyc/onboarding-link
→ ekyc_url (secure delivery by merchant to verified user)
GET /api/merchant/v1/users/{id}/kyc → APPROVED / REVIEW / PENDING / ...
Webhook: user.kyc.updated (optional)
2026-10-27: Explicit birthday/title/gender/nationality on every preload, applicants must be at least 18. Duplicate email/phone gives 409. Provider completion alone is not approval: final_review remains pending until SAKURA approval. Identity is locked after link issuance.
Every request uses existing Merchant HMAC headers. Base64 evidence is signed in JSON and forwarded to SAKURA as multipart. Never log or share eKYC bearer links with analytics services.
This is NOT an issued-cards endpoint. It returns MatchMove product codes, physical/virtual types, artwork, currencies, CVV metadata and issuer limits. Requires signed Merchant HMAC with cards.read and a connected SAKURA cards grant.
GET /api/merchant/v1/card-types
GET /api/merchant/v1/card-types?form_factor=physical
GET /api/merchant/v1/card-types?form_factor=virtual&code=seguammccard
| Query parameter | Value / validation |
|---|---|
form_factor | physical / virtual (optional) |
code | Exact code; letters, digits, _ and -, max 100 characters |
Response: data[] (id, code, name, description, form_factor, personalised_order, token, currency, image, details, created_at, updated_at), total, filters, meta. details may be an object or [] depending on provider data.
{"data":[{"id":1,"code":"seguammcpcard","name":"Sakura Exchange, Guam Inc. Physical Card","description":"Sakura Exchange, Guam Inc. Physical Card","form_factor":"physical","personalised_order":false,"token":{"type":"CVV","generation":"static"},"currency":["USD"],"image":{"large":"https://assets.mmvpay.com/global/products/seguammcpcard/card-large.png"},"details":{},"created_at":"2025-04-27 03:45:57","updated_at":"2026-10-29 02:30:15"}],"total":1,"filters":{"form_factor":"physical","code":null},"meta":{"source":"vrec_cache","stale":false,"refreshed_at":"2026-11-05T04:00:00+00:00","next_refresh_at":"2026-11-05T04:15:00+00:00","cache_ttl_seconds":900}}
Example JSON is illustrative. SAKURA refreshes the MatchMove catalog on a 900-second cycle and upserts mm_card_types; V-REC aligns its file-cache expiry to the SAKURA refresh deadline, so the delay does not accumulate. On provider failure, meta.stale=true indicates last-known data; 503 means no usable data. Catalog entries do not authorize card issuance.
HTTP: 200, 401, 403, 422, 429, 503. Cache-Control: private, no-store.
The default card list combines V-REC local cards with the attached identity’s existing personal Mastercard records in SAKURA. Requires cards.read, SAKURA cards grant, ACTIVE/FULL membership and can_view_balance=1. Cards issued to other V-REC programs and corporate Merchant-owned cards are excluded.
GET /api/merchant/v1/users/usr_example/cards
GET /api/merchant/v1/users/usr_example/cards?include_existing=false
GET /api/merchant/v1/users/usr_example/cards/skc_0123456789abcdef0123456789abcdef/transactions?from=2026-10-10&to=2026-11-09
data[].source indicates VREC_LOCAL / VREC_AND_SAKURA / SAKURA_ONLY; origin indicates VREC_LOCAL / VREC_ISSUED / SAKURA_EXISTING. SAKURA_ONLY cards have read-only skc_... IDs, which cannot be used for freeze, PIN, sensitive-card, or issuer-status APIs. Local card_... IDs are unchanged.
The card list does not include balances and is not a live issuer-status check. SAKURA may cap its inventory at 100 items (meta.sakura_truncated=true). SAKURA outages return 503, not a fabricated empty list. Use include_existing=false for the old V-REC-local-only response.
Card disclosure to a Merchant must have an appropriate legal basis and documented data-use purpose. Linking an identity does not automatically grant legal permission to disclose all separate financial activity.
The default card list combines V-REC local cards with the attached identity’s existing personal Mastercard records in SAKURA. Requires cards.read, SAKURA cards grant, ACTIVE/FULL membership and can_view_balance=1. Cards issued to other V-REC programs and corporate Merchant-owned cards are excluded.
GET /api/merchant/v1/users/usr_example/cards
GET /api/merchant/v1/users/usr_example/cards?include_existing=false
GET /api/merchant/v1/users/usr_example/cards/skc_0123456789abcdef0123456789abcdef/transactions?from=2026-10-10&to=2026-11-09
data[].source indicates VREC_LOCAL / VREC_AND_SAKURA / SAKURA_ONLY; origin indicates VREC_LOCAL / VREC_ISSUED / SAKURA_EXISTING. SAKURA_ONLY cards have read-only skc_... IDs, which cannot be used for freeze, PIN, sensitive-card, or issuer-status APIs. Local card_... IDs are unchanged.
The card list does not include balances and is not a live issuer-status check. SAKURA may cap its inventory at 100 items (meta.sakura_truncated=true). SAKURA outages return 503, not a fabricated empty list. Use include_existing=false for the old V-REC-local-only response.
Card disclosure to a Merchant must have an appropriate legal basis and documented data-use purpose. Linking an identity does not automatically grant legal permission to disclose all separate financial activity.
The default card list combines V-REC local cards with the attached identity’s existing personal Mastercard records in SAKURA. Requires cards.read, SAKURA cards grant, ACTIVE/FULL membership and can_view_balance=1. Cards issued to other V-REC programs and corporate Merchant-owned cards are excluded.
GET /api/merchant/v1/users/usr_example/cards
GET /api/merchant/v1/users/usr_example/cards?include_existing=false
GET /api/merchant/v1/users/usr_example/cards/skc_0123456789abcdef0123456789abcdef/transactions?from=2026-10-10&to=2026-11-09
data[].source indicates VREC_LOCAL / VREC_AND_SAKURA / SAKURA_ONLY; origin indicates VREC_LOCAL / VREC_ISSUED / SAKURA_EXISTING. SAKURA_ONLY cards have read-only skc_... IDs, which cannot be used for freeze, PIN, sensitive-card, or issuer-status APIs. Local card_... IDs are unchanged.
The card list does not include balances and is not a live issuer-status check. SAKURA may cap its inventory at 100 items (meta.sakura_truncated=true). SAKURA outages return 503, not a fabricated empty list. Use include_existing=false for the old V-REC-local-only response.
Card disclosure to a Merchant must have an appropriate legal basis and documented data-use purpose. Linking an identity does not automatically grant legal permission to disclose all separate financial activity.
These three feeds have different data sources and scopes. Successful card issuance does not guarantee a card purchase. Never merge wallet events into card statements or represent issuer activity as Merchant ledger entries.
| Data source | Merchant HMAC GET | What it returns |
|---|---|---|
| MatchMove wallet | /api/merchant/v1/users/{id}/matchmove-wallet-transactions | Wallet-level transfers, funding and related events; not filtered by card. |
| MatchMove card | /api/merchant/v1/users/{id}/cards/{cardId}/transactions | Issuer transactions for exactly one card; from/to up to 90 days. |
| V-REC Merchant ledger | /api/merchant/v1/users/{id}/transactions | Merchant-attributed balance changes; not complete issuer activity. |
Authentication and access: All three use signed Merchant HMAC with transactions.read. Live MatchMove feeds require ACTIVE/FULL membership and can_view_balance=1; the wallet endpoint additionally requires SAKURA Connected Apps balances while the card endpoint requires cards.
GET /api/merchant/v1/users/usr_example/matchmove-wallet-transactions?page=1&limit=20
GET /api/merchant/v1/users/usr_example/cards/card_example/transactions?from=2026-10-05&to=2026-11-04&page=1&limit=20
Any filter activates cursor pagination: pass next_cursor as the next cursor with exactly the same filters and limit. Do not increment page. Without filters, page/limit behavior is unchanged.
| Parameter | Purpose |
|---|---|
from / to | Issuer dates inclusive; interval max 90 days. |
category / type | Normalized category or exact MatchMove event type. |
indicator | debit / credit |
q / transaction_id | Text substring search or exact MatchMove transaction ID. |
min_amount / max_amount | Inclusive wallet amount, separate from original JPY amount and fee. |
transaction_mode / mcc | ECOM/POS/ATM or merchant category code. |
original_currency / currency / status | Original currency, wallet currency, provider status. |
include_zero_amount | If false, hide zero-value events. |
GET /api/merchant/v1/users/usr_example/matchmove-wallet-transactions?category=purchase&indicator=debit&from=2026-11-01&to=2026-11-04&limit=20
// next_cursorが返った場合(同じフィルターを繰り返す)
GET /api/merchant/v1/users/usr_example/matchmove-wallet-transactions?category=purchase&indicator=debit&from=2026-11-01&to=2026-11-04&limit=20&cursor=NEXT_CURSOR
V-REC does not expose one fungible wallet. Every source has its own system of record. Keep transaction IDs and reconcile related events; never blindly sum different ledgers.
Partner payout request. CREDITED indicates successful funding. A direct allocation is stored separately.
GET /api/v1/me/payoutsDirect funding from tenant to user. A COMPLETED allocation creates an attributed lot and ledger CREDIT; returns are separate.
GET /api/v1/me/tenant-allocationsV-REC ledger, local card entries, MatchMove issuer statements and SAKURA crypto events represent different records.
GET /api/v1/me/ledger-entries| Question | Use this API | Interpretation |
|---|---|---|
| Direct tenant funding received | GET /api/v1/me/tenant-allocations | COMPLETED / remaining_amount · USD |
| Value returned to tenant | GET /api/v1/me/tenant-returns | COMPLETED / PENDING · USD |
| Attributed ledger movements | GET /api/v1/me/ledger-entries | CREDIT / DEBIT / TENANT_RETURN |
| Corporate payouts | GET /api/v1/me/payouts | CREDITED ≠ direct Allocation |
| Internal card ledger | GET /api/v1/me/transactions | Not the full issuer statement |
| Actual card spending & ATM | GET /api/v1/me/cards/{id}/issuer-transactions | MatchMove via SAKURA |
| USDT wallet transfers | GET /api/v1/me/wallet/transfers | USDT / tx_hash |
| Purchases paid with crypto | GET /api/v1/me/wallet/purchases | Paid asset ≠ product delivered |
| Activity | Debit / transferred | Received | Ledger |
|---|---|---|---|
| Direct allocation | Tenant Fund · USD 10.00 | V-REC lot · USD 10.00 | Tenant Allocation + Ledger CREDIT |
| USDT Transfer | 12.345678 USDT | External chain address | SAKURA wallet transfers |
| Buy SGP-USD with USDT | 10 USDT | SGP-USD (quantity per contract) | SAKURA sale contract + separate SGP-USD lot |
| Buy points with merchant token | 125 custom-token units | Merchant points (per contract) | SAKURA sale contract / Merchant point ledger |
Merchant HMAC: POST /api/merchant/v1/tenant-funding/allocations
→ status = COMPLETED, allocation.public_id = vta_...
Authenticated V-REC user: GET /api/v1/me/tenant-allocations
→ data[...].public_id = vta_..., amount = "10.00000000", currency = "USD"
Authenticated V-REC user: GET /api/v1/me/ledger-entries
→ entry_type = CREDIT, reference_type = SAKURA_TENANT_ALLOCATION
User payouts API: GET /api/v1/me/payouts
→ { "data": [] } (VALID: no corporate payout took place)
Tap an API row to expand the complete contract for that endpoint. Required values marked conditional or dynamic depend on prior state or the current SAKURA KYCFORM definition.
Every request is signed by the Merchant credential. Timestamp must be within ±300 seconds; nonce must be unique and 8–128 characters. Credentials are confined to their Merchant boundary.
X-VREC-Merchant-Key: <value>
X-VREC-Timestamp: <value>
X-VREC-Nonce: <value>
X-VREC-Signature: <value>
METHOD + "\n" + PATH + "\n" + CANONICAL_QUERY + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + SHA256(raw_body)
Same-origin V-REC user session. Unauthenticated requests are HTTP 302 redirects to /v/{instance}/login, not JSON 401. POST requires form field _csrf; mismatch is an HTTP 419 non-JSON body.
Do not recreate an existing SAKURA identity through POST /api/merchant/v1/users. The Merchant must not select an identity by email or SAKURA user id. Create a consent Link Request, give its authorization_url to the user, and only the SAKURA identity authenticated and approved by that user is attached to V-REC.
Merchant backend
POST /api/merchant/v1/user-links + Idempotency-Key
↓ authorization_url
User opens SAKURA consent page
↓ sign in + approve
SAKURA → V-REC callback (one-time code + state)
↓ code exchange / stable SAKURA user UUID
V-REC creates or reuses Global User + Tenant membership
↓
Merchant polls GET /api/merchant/v1/user-links/{id}
↓
COMPLETED + linked_user_id + membership(FULL or READONLY)
The wallet is SAKURA's unhosted ERC wallet. The 12-word BIP-39 seed phrase is returned exactly once after create/rotate, and the private key is encrypted with the user's wallet password. V-REC never stores the seed phrase, key or wallet password. For gasless transfers SAKURA's relayer pays the gas (ETH).
V-REC user (session)
POST /api/v1/me/wallet current_password step-up + wallet_password
↓ 201 + mnemonic (once) → write down 12 words
GET /api/v1/me/wallet address + USDT/ETH balances + gasless eligibility
POST /api/v1/me/wallet/address-check (address poisoning check)
POST /api/v1/me/wallet/gasless-transfer Idempotency-Key + wallet_password
↓ 201 SUBMITTED + tx_hash | 202 transfer_status_unknown → GET /api/v1/me/wallet/requests/{key}
POST /api/v1/me/wallet/rotate step-up + new_wallet_password + reason → new mnemonic (once)
V-REC server → SAKURA Connected Apps (scopes crypto_wallet / crypto_wallet_manage)
/api/integrations/vrec/users/{ref}/wallet* Bearer + X-VRec-Proof, idempotency_key per write
Users purchase SGP-USD or merchant-defined points, paying with USDT or the merchant token. Sold under the SAKURA EXCHANGE licence; not a currency exchange. Each purchase fixes a contract number, seller, price and fees, then the user accepts and pays with the wallet password.
| Goods | Paid in | Seller | Price goes to | Merchant revenue |
|---|---|---|---|---|
| SGP-USD | USDT | SAKURA EXCHANGE | Company USDT sales address | Royalty (settled by the company) |
| Merchant points | USDT | Merchant | Company USDT sales address | Net proceeds + royalty (settled by the company) |
| SGP-USD / points | Custom token | Merchant (sole responsibility) | Merchant address (company never holds it) | Keeps the royalty; goods backed by merchant USDT pool |
GET /api/v1/me/wallet/purchase-catalog
POST /api/v1/me/wallet/purchase-quotes Idempotency-Key → contract_no (QUOTED, 10 min)
POST /api/v1/me/wallet/purchases/{contract}/confirm accept_terms + wallet_password → PAYMENT_SUBMITTED
… 12 confirmations → COMPLETED (SGP-USD as merchant-route lot / points)
GET /api/merchant/v1/crypto-sales (Merchant HMAC, crypto_sales.read)
https://api.v-rec.xyz
/api/merchant/v1/card-types
List MatchMove card products (15-min cache)
cards.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
form_factor | string | no | physical or virtualphysical|virtual |
code | string | no | Exact card product code^[A-Za-z0-9_-]+$, maxLength=100 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array<object> | yes | Card product records including all stored metadatano card issuance authorization implied |
data[].id / code / name / description | integer / string | yes | Product ID, code, name and descriptioncode is the exact MatchMove product identifier |
data[].form_factor / personalised_order | string / boolean | yes | physical/virtual and personalization flagform_factor=physical|virtual |
data[].token / currency / image / details | object / array / object / object|array | yes | CVV configuration, currencies, image URLs and issuance limit metadataJSON decoded, not serialized strings; details can be [] |
data[].created_at / updated_at | string|null | yes | DB creation and update timestampsYYYY-MM-DD HH:mm:ss when present |
meta.source / meta.stale / meta.refreshed_at / meta.next_refresh_at / meta.cache_ttl_seconds | string / boolean / datetime|null / datetime / integer | yes | Data source, stale flag, last MatchMove refresh time, refresh deadline and cache TTL900 seconds on SAKURA; stale=true means last-known data |
total / filters | integer / object | yes | Filtered count and applied search parametersfilters.form_factor and filters.code are nullable |
{
"data": [
{
"id": 1,
"code": "seguammcpcard",
"name": "Sakura Exchange, Guam Inc. Physical Card",
"description": "Sakura Exchange, Guam Inc. Physical Card",
"form_factor": "physical",
"personalised_order": false,
"token": {
"type": "CVV",
"generation": "static"
},
"currency": [
"USD"
],
"image": {
"large": "https://assets.mmvpay.com/global/products/seguammcpcard/card-large.png"
},
"details": {
"active_limits": {
"allowed": 1,
"pre_kyc": 0,
"post_kyc": 5
}
},
"created_at": "2025-04-27 03:45:57",
"updated_at": "2026-10-29 02:30:15"
}
],
"total": 1,
"filters": {
"form_factor": "physical",
"code": null
},
"meta": {
"source": "vrec_cache",
"stale": false,
"refreshed_at": "2026-11-05T04:00:00+00:00",
"next_refresh_at": "2026-11-05T04:15:00+00:00",
"cache_ttl_seconds": 900
}
}
merchant_auth_required / merchant_signature_invalidFormat: JSONHMAC authentication failed
merchant_scope_denied / sakura_cards_scope_requiredFormat: JSONcards.read or SAKURA cards scope denied
invalid_form_factor / invalid_codeFormat: JSONInvalid query filter
card_types_unavailable / card_types_upstream_unavailableFormat: JSONLocal database or upstream catalog unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/context
Get authenticated Merchant context
users.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
merchant.id | string | yes | Merchant public IDmrc_... |
merchant.name | string | yes | Merchant display name |
merchant.program_id | string | yes | V-REC program public ID |
{
"merchant": {
"id": "mrc_demo",
"name": "Example Merchant",
"program_id": "vrec_demo"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/profile-options
Get authoritative SAKURA country/dial-code options
users.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
countries[] | array<object> | yes | Country candidates allowed by SAKURA |
countries[].code | string | yes | Country code to submit^[A-Z]{2}$ |
countries[].provider_code | string | yes | Compatibility metadata; empty string when current SAKURA endpoint omits itdo not submit this instead of code |
countries[].label | string | yes | Display label |
dial_codes[] | array<object> | yes | Dial-code candidates allowed by SAKURA |
dial_codes[].code | string | yes | Dial code to submit+digits |
dial_codes[].provider_code | string | yes | Compatibility metadata; empty string when current SAKURA endpoint omits it |
dial_codes[].label | string | yes | Display label |
generated_at | string|null | yes | Catalog generation timestampISO 8601 when non-null |
{
"countries": [
{
"code": "JP",
"provider_code": "",
"label": "Japan"
}
],
"dial_codes": [
{
"code": "+81",
"provider_code": "",
"label": "Japan (+81)"
}
],
"generated_at": "2026-10-05T01:00:00Z"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
profile_options_unavailableFormat: JSONSAKURA option catalog is unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users
List users owned by the Merchant
users.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
limit | integer | no | Page sizePHP (int) cast then clamp 1..100; omitted=>50; e.g. abc/0/negative=>1, >100=>100 |
offset | integer | no | OffsetPHP (int) cast then max(0,...); omitted=>0; abc/negative=>0 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array<merchant_user> | yes | Merchant-owned user array |
data[].id | string | yes | V-REC user public IDusers.public_id; usr_... |
data[].beneficiary_id | string | yes | Beneficiary IDusers.beneficiary_id |
data[].email | string | yes | Current SAKURA email; V-REC stored email fallbackemail |
data[].name | string | yes | Current SAKURA name; empty string if remote name is absent |
data[].profile | object | yes | Current SAKURA profile projection |
data[].profile.first_name | string | conditional | SAKURA canonical first nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.last_name | string | conditional | SAKURA canonical last nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.country_code | string | conditional | SAKURA canonical country codecurrent canonical projection; normally ISO alpha-2; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.dial_code | string | conditional | SAKURA canonical dial codenormalized +digits; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.phone | string | conditional | SAKURA canonical phonetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.postal_code | string | conditional | SAKURA canonical postal codetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.address_line1 | string | conditional | SAKURA canonical address line 1trimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.address_line2 | string | conditional | SAKURA canonical address line 2trimmed string; key always present; may be empty; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.city | string | conditional | SAKURA canonical citytrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].profile.state | string | conditional | SAKURA canonical state/prefecturetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
data[].country | string|null | yes | profile.country_code; null when absentISO alpha-2 when non-null |
data[].kyc_status | string | yes | Current SAKURA KYC status with V-REC fallbackNOT_STARTED|PENDING|APPROVED|REJECTED|REVIEW|CORRECTION_REQUIRED |
data[].status | string | yes | Membership status for this Merchantnormally ACTIVE|SUSPENDED; INACTIVE rows are excluded |
data[].global_status | string | yes | Global V-REC user status |
data[].sakura_user_ref | string|null | yes | Current SAKURA user referencekey is always present; value may be null |
data[].created_at | string | yes | V-REC creation timestampDB datetime |
data[].updated_at | string | yes | V-REC update timestampDB datetime |
limit | integer | yes | Applied limit1..100 after PHP integer-cast + clamp |
offset | integer | yes | Applied offset>=0 after PHP integer-cast + clamp |
{
"data": [
{
"id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"email": "[email protected]",
"name": "Taro Yamada",
"profile": {
"country_code": "JP"
},
"country": "JP",
"kyc_status": "APPROVED",
"status": "ACTIVE",
"global_status": "ACTIVE",
"sakura_user_ref": "USR_DEMO",
"created_at": "2026-10-01 00:00:00",
"updated_at": "2026-10-05 00:00:00"
}
],
"limit": 50,
"offset": 0
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
sakura_profile_unavailableFormat: JSONSAKURA profile batch fetch failed
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users
Create or safely link a V-REC/SAKURA user
users.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | yes | Creation idempotency key1..190 chars |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
email | string | yes | End-user emailvalid email; SAKURA max 190 |
first_name | string | yes | First nametrimmed; non-empty; SAKURA max 120 |
last_name | string | yes | Last nametrimmed; non-empty; SAKURA max 120 |
country_code | string | conditional | Preferred country code; must be countries[].coderequired unless legacy country is used; if key exists as empty string, legacy country is NOT used; SAKURA size 2 |
country | string | conditional | Legacy alias used only when country_code is missing/nullsame allowed catalog as country_code; do not send both |
dial_code | string | yes | Dial code from profile-optionsnormalized to +digits; SAKURA max 12 |
phone | string | yes | Phone numbertrim must be non-empty; SAKURA max 40 |
postal_code | string | yes | Postal codetrim must be non-empty; SAKURA max 40 |
state | string | yes | State/prefecturetrim must be non-empty; SAKURA max 120 |
city | string | yes | Citytrim must be non-empty; SAKURA max 120 |
address_line1 | string | yes | Address line 1trim must be non-empty; SAKURA max 190 |
address_line2 | string | yes | Address line 2trim must be non-empty in current V-REC flow; SAKURA max 190 |
notifications | object | no | Onboarding email controlsomitted/null => both flags true; non-object => notifications_invalid; unknown keys ignored |
notifications.send_vrec_activation_email | boolean | no | Send V-REC activation emaildefault true; non-boolean when provided => notification_flag_invalid |
notifications.send_sakura_password_setup_email | boolean | no | Send SAKURA setup emaildefault true; non-boolean when provided => notification_flag_invalid |
onboarding_mode | string | no | KYC-only registration with suppressed setup mailekyc_only only; no password returned |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user.public_id | string | yes | V-REC user public IDusr_... |
user.beneficiary_id | string | yes | Beneficiary ID |
user.email | string | yes | Registered emailemail |
user.name | string | yes | Full name |
user.country | string|null | yes | SAKURA identity.profile.country_code; nullable in non-connected/mock branchISO alpha-2 when non-null |
user.kyc_status | string | yes | KYC status |
user.status | string | yes | Merchant membership status |
user.global_status | string | yes | Global user status |
user.sakura_user_ref | string|null | yes | SAKURA user referencekey always present |
user.sakura_customer_uuid | string | yes | SAKURA stable customer UUIDnon-empty or request fails |
user.created_at | string | yes | Created timestamp |
user.activation_url | string | conditional | Returned only when a new local V-REC login is createdomitted for already-linked user; SAKURA password_setup_url is never returned |
user.activation_expires_in | integer | conditional | activation_url lifetime in secondspresent with activation_url; 86400 |
user.notifications.vrec_activation_email | string | yes | V-REC activation email resultsent|disabled|not_applicable|failed |
user.notifications.sakura_password_setup_email | string | yes | SAKURA password-setup email resultsent|disabled|not_applicable|failed |
{
"user": {
"public_id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"email": "[email protected]",
"name": "Taro Yamada",
"country": "JP",
"kyc_status": "NOT_STARTED",
"status": "ACTIVE",
"global_status": "ACTIVE",
"sakura_user_ref": "USR_DEMO",
"sakura_customer_uuid": "cust_demo",
"created_at": "2026-10-05 01:00:00",
"activation_url": "https://v-rec.xyz/activate/token",
"activation_expires_in": 86400,
"notifications": {
"vrec_activation_email": "sent",
"sakura_password_setup_email": "sent"
}
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
invalid_jsonFormat: JSONMalformed JSON body
idempotency_key_reused_with_different_request / request_in_progress / email_identity_mismatch / merchant_context_required / user_link_not_authorized / email_already_registeredFormat: JSONIdempotency or identity conflict
idempotency_key_required / invalid_email / name_required / country_required / dial_code_required / phone_required / postal_code_required / state_required / city_required / address_line1_required / address_line2_required / invalid_country_code / invalid_dial_code / notifications_invalid / notification_flag_invalid / sakura_customer_uuid_missing / sakura_link_incompleteFormat: JSONInput validation failure
upstream_or_internal_error / <SAKURA errorCode or sakura_unavailable>Format: JSONSAKURA/upstream temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/user-links
Create existing SAKURA user consent link
users.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | yes | Idempotency key for the same link request1..190 chars; reuse only for identical external_customer_ref |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
external_customer_ref | string|null | no | Merchant customer reference. Never used to select the SAKURA identity.max 190 |
No body or no endpoint-specific response schema.
{
"link_request": {
"id": "ulr_demo",
"status": "PENDING",
"authorization_url": "https://payment.sakura-guam.com/vrec/user-link/vul_demo",
"external_customer_ref": "customer-123",
"expires_at": "2026-10-05T12:15:00Z",
"linked_user_id": null,
"activation_email_status": null
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
idempotency_key_required / idempotency_key_reused_with_different_request / sakura_not_connected / sakura_target_merchant_mismatch / sakura_link_request_mismatchFormat: JSONIdempotency conflict or SAKURA connection not ready
link_request_unavailable / sakura_link_unavailableFormat: JSONTemporary SAKURA/upstream failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/user-links/{id}
Get existing-user link request status
users.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | Link request public IDulr_... |
No body or no endpoint-specific response schema.
{
"link_request": {
"id": "ulr_demo",
"status": "COMPLETED",
"authorization_url": "https://payment.sakura-guam.com/vrec/user-link/vul_demo",
"linked_user_id": "usr_demo",
"activation_email_status": "sent",
"completed_at": "2026-10-05T12:03:00Z",
"membership": {
"status": "ACTIVE",
"access_mode": "READONLY",
"source_merchant_id": "MRC_00000001",
"can_receive_tenant_funds": false,
"can_spend": false
}
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
link_request_not_foundFormat: JSONNo such request inside this Merchant boundary
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}
Get one Merchant-owned user
users.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusers.public_id; usr_... |
beneficiary_id | string | yes | Beneficiary IDusers.beneficiary_id |
email | string | yes | Current SAKURA email; V-REC stored email fallbackemail |
name | string | yes | Current SAKURA name; empty string if remote name is absent |
profile | object | yes | Current SAKURA profile projection |
profile.first_name | string | conditional | SAKURA canonical first nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.last_name | string | conditional | SAKURA canonical last nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.country_code | string | conditional | SAKURA canonical country codecurrent canonical projection; normally ISO alpha-2; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.dial_code | string | conditional | SAKURA canonical dial codenormalized +digits; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.phone | string | conditional | SAKURA canonical phonetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.postal_code | string | conditional | SAKURA canonical postal codetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.address_line1 | string | conditional | SAKURA canonical address line 1trimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.address_line2 | string | conditional | SAKURA canonical address line 2trimmed string; key always present; may be empty; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.city | string | conditional | SAKURA canonical citytrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.state | string | conditional | SAKURA canonical state/prefecturetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
country | string|null | yes | profile.country_code; null when absentISO alpha-2 when non-null |
kyc_status | string | yes | Current SAKURA KYC status with V-REC fallbackNOT_STARTED|PENDING|APPROVED|REJECTED|REVIEW|CORRECTION_REQUIRED |
status | string | yes | Membership status for this Merchantnormally ACTIVE|SUSPENDED; INACTIVE rows are excluded |
global_status | string | yes | Global V-REC user status |
sakura_user_ref | string|null | yes | Current SAKURA user referencekey is always present; value may be null |
created_at | string | yes | V-REC creation timestampDB datetime |
updated_at | string | yes | V-REC update timestampDB datetime |
{
"id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"email": "[email protected]",
"name": "Taro Yamada",
"profile": {
"country_code": "JP"
},
"country": "JP",
"kyc_status": "APPROVED",
"status": "ACTIVE",
"global_status": "ACTIVE",
"sakura_user_ref": "USR_DEMO",
"created_at": "2026-10-01 00:00:00",
"updated_at": "2026-10-05 00:00:00"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSONUser does not exist within this Merchant boundary
sakura_profile_unavailableFormat: JSONSAKURA profile unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/status
Set Merchant membership ACTIVE/SUSPENDED
users.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
status | string | yes | Membership statusACTIVE|SUSPENDED; trim + strtoupper => case-insensitive input |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusers.public_id; usr_... |
beneficiary_id | string | yes | Beneficiary IDusers.beneficiary_id |
email | string | yes | Current SAKURA email; V-REC stored email fallbackemail |
name | string | yes | Current SAKURA name; empty string if remote name is absent |
profile | object | yes | Current SAKURA profile projection |
profile.first_name | string | conditional | SAKURA canonical first nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.last_name | string | conditional | SAKURA canonical last nametrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.country_code | string | conditional | SAKURA canonical country codecurrent canonical projection; normally ISO alpha-2; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.dial_code | string | conditional | SAKURA canonical dial codenormalized +digits; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.phone | string | conditional | SAKURA canonical phonetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.postal_code | string | conditional | SAKURA canonical postal codetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.address_line1 | string | conditional | SAKURA canonical address line 1trimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.address_line2 | string | conditional | SAKURA canonical address line 2trimmed string; key always present; may be empty; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.city | string | conditional | SAKURA canonical citytrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
profile.state | string | conditional | SAKURA canonical state/prefecturetrimmed string; key always present; present when SAKURA profile projection includes this field; MerchantApiController may return profile={} when remote profile data is absent |
country | string|null | yes | profile.country_code; null when absentISO alpha-2 when non-null |
kyc_status | string | yes | Current SAKURA KYC status with V-REC fallbackNOT_STARTED|PENDING|APPROVED|REJECTED|REVIEW|CORRECTION_REQUIRED |
status | string | yes | Membership status for this Merchantnormally ACTIVE|SUSPENDED; INACTIVE rows are excluded |
global_status | string | yes | Global V-REC user status |
sakura_user_ref | string|null | yes | Current SAKURA user referencekey is always present; value may be null |
created_at | string | yes | V-REC creation timestampDB datetime |
updated_at | string | yes | V-REC update timestampDB datetime |
{
"id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"email": "[email protected]",
"name": "Taro Yamada",
"profile": {
"country_code": "JP"
},
"country": "JP",
"kyc_status": "APPROVED",
"status": "SUSPENDED",
"global_status": "ACTIVE",
"sakura_user_ref": "USR_00000001",
"created_at": "2026-10-01 00:00:00",
"updated_at": "2026-10-05 00:00:00"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
invalid_jsonFormat: JSONMalformed JSON
not_foundFormat: JSONUser outside Merchant boundary
invalid_statusFormat: JSONStatus is not ACTIVE or SUSPENDED
sakura_profile_unavailableFormat: JSONPost-update SAKURA profile unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc
Synchronize and read KYC status from SAKURA
kyc.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public IDusr_... |
kyc_status | string | yes | KYC status synchronized from SAKURANOT_STARTED|PENDING|APPROVED|REJECTED|REVIEW|CORRECTION_REQUIRED |
checked_at | string | yes | Last sync timestamp updated by this requestDB UTC datetime |
{
"user_id": "usr_demo",
"kyc_status": "APPROVED",
"checked_at": "2026-10-05 01:00:00"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSONUser outside Merchant boundary
kyc_sync_unavailableFormat: JSONKYC synchronization unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/preload-status
Read KYC status and owner
kyc.preload
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | Merchant-owned user ID |
No body or no endpoint-specific response schema.
{
"user_id": "usr_example",
"stage": "final_review",
"kyc_status": "PENDING",
"next_action": {
"type": "final_review"
},
"responsibility": {
"owner": "SAKURA_REVIEWER"
}
}
validation_errorField missing / applicant younger than 18
previous_preload_unresolvedPrior preload is unresolved
sakura_unavailableSAKURA temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/preload-reconcile
Reconcile approved correction
kyc.preload
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | Merchant-owned user ID |
No body or no endpoint-specific response schema.
{
"user_id": "usr_example",
"stage": "final_review",
"kyc_status": "PENDING",
"next_action": {
"type": "final_review"
},
"responsibility": {
"owner": "SAKURA_REVIEWER"
}
}
validation_errorField missing / applicant younger than 18
previous_preload_unresolvedPrior preload is unresolved
sakura_unavailableSAKURA temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/start
Get handoff information for end-user KYC
kyc.start
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public ID |
kyc_status | string | yes | Current KYC status |
action | string | yes | Action required by merchantUSER_LOGIN_REQUIRED |
user_url | string | yes | V-REC URL where the end user authenticateshttps URL |
message | string | yes | Implementation message |
{
"user_id": "usr_demo",
"kyc_status": "NOT_STARTED",
"action": "USER_LOGIN_REQUIRED",
"user_url": "https://v-rec.xyz/v/program/login",
"message": "The user must authenticate to V-REC before KYC can be started. No SAKURA impersonation URL is returned to merchant servers."
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSONUser outside Merchant boundary
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards
List pre-existing SAKURA and V-REC cards for an attached user
cards.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
include_existing | boolean | no | Include existing SAKURA cardsdefault true; false/0 is V-REC local-only legacy mode; true/1 accepted |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public IDusers.public_id; current generated IDs use usr_... |
data[] | array<card_list_item> | yes | Masked card listPAN/CVV/PIN excluded |
data[].id | string | yes | Local V-REC card_... or read-only SAKURA inventory skc_... IDcard_... is local; skc_... is scoped to user/program and read-only |
data[].card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
data[].brand | string | yes | Card brandMASTERCARD |
data[].masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
data[].last4 | string|null | yes | Last four PAN digitsnullable |
data[].currency | string|null | yes | Card currencySAKURA or local currency (3 letters), unknown null |
data[].status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
data[].atm_enabled | boolean|null | yes | ATM permission if known, otherwise nullunknown for SAKURA_ONLY; no false inference |
data[].created_at | string|null | yes | Card record creation timenull for card absent from V-REC local DB |
meta | object | no | SAKURA inventory metadataonly include_existing=true; includes truncation status |
data[].source | string | yes | Read sourceVREC_LOCAL | VREC_AND_SAKURA | SAKURA_ONLY |
data[].origin | string | yes | Card originationVREC_LOCAL | VREC_ISSUED | SAKURA_EXISTING |
data[].read_only | boolean | yes | Read-only identifiertrue for SAKURA_ONLY; no mutation permissions are ever inferred |
data[].status_source | string | yes | Status provenanceVREC_LOCAL_RECORD | SAKURA_CARD_RECORDS; NOT MatchMove live |
meta.sakura_truncated | boolean | no | SAKURA upstream 100-card truncationtrue means incomplete list |
meta.inventory_complete | boolean | no | SAKURA list not truncatedfalse when sakura_truncated=true |
meta.sakura_is_live | boolean | no | Not a live issuer statealways false |
{
"user_id": "usr_demo",
"data": [
{
"id": "card_demo",
"card_type": "VIRTUAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": false,
"created_at": "2026-10-01 00:00:00",
"source": "VREC_AND_SAKURA",
"origin": "VREC_ISSUED",
"read_only": false,
"status_source": "SAKURA_CARD_RECORDS"
},
{
"id": "skc_0123456789abcdef0123456789abcdef",
"card_type": "PHYSICAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 5678",
"last4": "5678",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": null,
"created_at": null,
"source": "SAKURA_ONLY",
"origin": "SAKURA_EXISTING",
"read_only": true,
"status_source": "SAKURA_CARD_RECORDS"
}
],
"meta": {
"includes_existing": true,
"sakura_source": "SAKURA_CARD_RECORDS",
"sakura_is_live": false,
"sakura_truncated": false,
"inventory_complete": true,
"total_returned": 2
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_deniedFormat: JSONMissing cards.read grant
not_foundFormat: JSONUser outside Merchant scope
cards_overview_requires_connected_apps / sakura_cards_scope_required / sakura_user_not_linkedFormat: JSONSAKURA connection or user link missing
invalid_include_existingFormat: JSONInvalid include_existing value
too_many_attemptsFormat: JSONInventory rate limit exceeded
sakura_card_inventory_unavailable / membership_capabilities_unavailableFormat: JSONUpstream unavailable / capability migration incomplete, not zero cards
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards
Issue VIRTUAL card by Merchant API
cards.issue
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | yes | Idempotency key8-190 chars; mandatory |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
card_type | string | yes | Card typeVIRTUAL only |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user IDusr_... |
card | object | yes | Masked issued cardno PAN/CVV |
idempotent_replay | boolean | yes | Present for idempotent replay onlytrue on replay |
{
"user_id": "usr_demo",
"card": {
"id": "card_demo",
"card_type": "VIRTUAL",
"masked_pan": "**** **** **** 1234"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_denied / full_membership_required / merchant_membership_capability_denied / user_not_activeFormat: JSONScope, membership, capability, or active-user requirement not met
not_foundFormat: JSONUser not found in Merchant scope
idempotency_key_reused_with_different_request / card_issue_reconciliation_required / card_issue_in_progressFormat: JSONIdempotency conflict, unresolved issuer outcome or atomic-operation race
idempotency_key_required / only_virtual_card_type_supported / card_issue_in_progress / kyc_not_approved / card_issue_pre_submission_rejected / card_issue_confirmed_not_issuedFormat: JSONInvalid request or definite pre-submission failure; a preflight card_issue_in_progress may return 422
request_too_largeFormat: JSONRequest body too large
card_issue_reconciliation_required / card_issue_operation_unavailableFormat: JSONUncertain issuer outcome or operation storage unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/issuance/{key}
Read virtual-card issuance outcome (no issuer resubmission)
cards.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
key | string | yes | Idempotency-Key used for issuance8–190 chars; A-Z a-z 0-9 . _ : - |
No body or no endpoint-specific response schema.
{
"user_id": "usr_demo",
"status": "COMPLETED",
"card_id": "card_demo",
"reconciliation_required": false,
"automatic_resubmission_allowed": false,
"updated_at": "2026-10-23 00:00:00"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_denied / full_membership_required / merchant_membership_capability_denied / user_not_activeFormat: JSONMissing read scope or active FULL membership
not_foundFormat: JSONNo matching issuance operation for Merchant and user
invalid_operation_keyFormat: JSONInvalid operation key
membership_capabilities_unavailableFormat: JSONMembership capability data unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/{cardId}/transactions
MatchMove card history for V-REC or existing SAKURA cards
transactions.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
cardId | string | yes | card_... or skc_... from the card listcard_... V-REC local; skc_... SAKURA_ONLY read-only |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
from | date | no | Start dateYYYY-MM-DD; default 30 days ago; maximum date span 90 days |
to | date | no | End dateYYYY-MM-DD; default today; no later than UTC tomorrow |
page | integer | no | Pagedefault 1; 1-100 |
limit | integer | no | Limitdefault 20; 1-50 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC public user IDusr_... |
card_id | string | yes | V-REC public card IDcard_... |
source | string | yes | Issuer card history sourcematchmove |
page | integer | yes | Page number1-100 |
limit | integer | yes | Requested page size1-50 |
from | date | yes | Start dateYYYY-MM-DD |
to | date | yes | End dateYYYY-MM-DD; max span 90 days |
total | integer|null | yes | Issuer total record count (null if unavailable)nullable |
records_per_page | integer | yes | Issuer page capacity actually usedused for next-page calculation |
has_more | boolean | yes | More pages may be availablebest effort |
items | array<object> | yes | Per-card issuer transactions[] for valid no-transaction result |
items[].id | string | conditional | Transaction IDprovider value may be blank |
items[].type | string | conditional | Card transaction typeprovider value |
items[].amount | decimal string|null | conditional | Amountnullable |
items[].currency | string | conditional | Currencyprovider value |
items[].description | string | conditional | Descriptionprovider value |
items[].merchant | string | conditional | Merchant nameprovider value |
items[].status | string | conditional | Transaction statusprovider value |
items[].date | string | conditional | Transaction timestampprovider string |
items[].indicator | string|null | conditional | Debit/credit indicatornullable |
{
"user_id": "usr_demo",
"card_id": "card_demo",
"items": [
{
"id": "EXAMPLE_CARD_TX",
"type": "PURCHASE",
"amount": "12.50",
"currency": "USD",
"description": "Example Store",
"merchant": "Example Store",
"status": "APPROVED",
"date": "2026-11-04T01:00:00Z",
"indicator": "debit"
}
],
"page": 1,
"limit": 20,
"from": "2026-10-05",
"to": "2026-11-04",
"total": 1,
"records_per_page": 20,
"has_more": false,
"source": "matchmove"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_deniedFormat: JSONNo permission
card_not_foundFormat: JSONCard not found
invalid_filtersFormat: JSONInvalid filter
too_many_attemptsFormat: JSONRate limited
issuer_transactions_unavailableFormat: JSONIssuer unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/matchmove-wallet-transactions
Live MatchMove wallet transaction history (not per-card)
transactions.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC public user IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
page | integer | no | Normal page; absent or 1 with search filters1-100; default 1; filtered search only page=1 |
limit | integer | no | Page size1-50; default 20 |
cursor | string | no | Opaque next_cursor. Repeat all search filtersmax 512; search mode only |
from | date | no | Inclusive issuer date startYYYY-MM-DD; from/to span <= 90d |
to | date | no | Inclusive issuer date endYYYY-MM-DD; from/to span <= 90d |
category | string | no | Normalized transaction categorypurchase|topup|transfer|atm|fee|authorization|card_issuance|adjustment|other |
type | string | no | Exact MatchMove type, case-insensitivefree string max 80; e.g. Purchase |
indicator | string | no | Wallet credit/debit directioncredit|debit |
q | string | no | Case-insensitive merchant/description/type/id substringmax 120 |
min_amount | decimal string | no | Inclusive gross wallet amount lower boundmax 8 decimals |
max_amount | decimal string | no | Inclusive gross wallet amount upper boundmax 8 decimals |
currency | string | no | Wallet currencyISO 4217 / USD |
original_currency | string | no | Original transaction currencyISO 4217 / JPY |
status | string | no | Exact provider statuse.g. Success |
transaction_mode | string | no | Card transaction modeecom|pos|atm |
mcc | string | no | Merchant category codefour digits |
transaction_id | string | no | Exact MatchMove transaction IDmax 190 |
include_zero_amount | boolean | no | If false exclude 0-value transactionstrue|false; default true |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC public user IDusr_... |
source | string | yes | Live MatchMove wallet sourceMATCHMOVE_WALLET_LIVE |
observed_at | ISO 8601 string | yes | Fetch timestamp from SAKURAUTC |
page | integer | yes | Page number1-100 |
limit | integer | yes | Requested page size1-50 |
total_records | integer|null | yes | Upstream record count; null when unknownnullable |
has_more | boolean | yes | Whether a further page may existbest-effort if upstream count unknown |
items | array<object> | yes | MatchMove wallet activity; empty only for valid zero-record responseno card ID filter |
items[].id | string|null | conditional | MatchMove transaction identifiernullable |
items[].transaction_date | string|null | conditional | Transaction timestampnullable; provider string |
items[].type | string|null | conditional | Event type (e.g. GET CARD)provider-specific |
items[].description | string|null | conditional | Merchant or activity descriptionnullable |
items[].indicator | string|null | conditional | Debit/credit indicatordebit / credit / null |
items[].amount | decimal string|null | conditional | Transaction amountnullable; do not float-round |
items[].currency | string|null | conditional | Currency codenullable; do not assume USD |
items[].status | string|null | conditional | MatchMove transaction statusprovider-specific |
pagination_mode | string | conditional | Additional cursor search metadataconditional |
next_cursor | string|null | conditional | Additional cursor search metadatanullable in filter mode |
search_complete | boolean | conditional | Additional cursor search metadataconditional |
scanned_records | integer | conditional | Additional cursor search metadataconditional |
filters | object | conditional | Additional cursor search metadatanullable in filter mode |
items[].category | string | conditional | Safe additional MatchMove field, null when absentconditional |
items[].balance | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].merchant_name | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].merchant_category_code | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].merchant_country | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].transaction_mode | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].original_amount | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].original_currency | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].fee_amount | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
items[].fee_currency | string|null | conditional | Safe additional MatchMove field, null when absentconditional |
{
"user_id": "usr_demo",
"source": "MATCHMOVE_WALLET_LIVE",
"observed_at": "2026-11-04T08:00:00Z",
"page": 1,
"limit": 20,
"total_records": 1,
"has_more": false,
"items": [
{
"id": "EXAMPLE_TRANSACTION_ID",
"transaction_date": "2026-11-04T01:00:00Z",
"type": "GET CARD",
"description": "PP Consumer",
"indicator": "debit",
"amount": "0.00",
"currency": "USD",
"status": "SUCCESS",
"category": "card_issuance",
"balance": "0.00",
"merchant_name": null
}
]
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONInvalid Merchant HMAC credentials, signature, timestamp or nonce
merchant_scope_denied / user_not_active / full_membership_required / merchant_membership_capability_deniedFormat: JSONNo transactions.read, ACTIVE/FULL membership, or balance-view permission
not_foundFormat: JSONUser not available to this Merchant
matchmove_wallet_not_linked / matchmove_wallet_identity_conflict / sakura_balances_scope_requiredFormat: JSONMatchMove wallet missing / identity mismatch / SAKURA balances scope not granted
invalid_filtersFormat: JSONInvalid filters, date range, cursor or pagination mode
too_many_attemptsFormat: JSON30 requests per minute per Merchant/user
wallet_transactions_unavailable / membership_capabilities_unavailableFormat: JSONSAKURA/MatchMove upstream unavailable; never represent as zero results
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/{cardId}/status
Freeze/suspend or unfreeze an owned card
cards.manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user idusr_... |
cardId | string | yes | V-REC card idcard_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
status | string | yes | FREEZE or SUSPEND becomes FROZEN; UNFREEZE becomes ACTIVEACTIVE|ACTIVATE|UNFREEZE|FROZEN|FREEZE|SUSPEND|SUSPENDED |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user IDusr_... |
card.id | string | yes | Card public IDcard_... |
card.status | string | yes | Verified card statusACTIVE or FROZEN |
effective_status | string | yes | Requested issuer-effective statusACTIVE or FROZEN |
{
"user_id": "usr_example",
"card": {
"id": "card_example",
"status": "FROZEN"
},
"effective_status": "FROZEN"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_deniedFormat: JSONNo permission
card_not_foundFormat: JSONCard not found
closed_card_cannot_reactivate / card_status_update_rejected / active_merchant_membership_requiredFormat: JSONDefinitive local refusal such as a closed card; ambiguous issuer outcomes return 503
unsupported_card_statusFormat: JSONUnsupported status
too_many_attemptsFormat: JSONRate limited
card_status_result_unconfirmedFormat: JSONIssuer status unconfirmed
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/{cardId}/sensitive-link
Create hosted cardholder password challenge
cards.sensitive.link
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
cardId | string | yes | V-REC card public IDcard_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
url | string | yes | Hosted V-REC password confirmation URLHTTPS; one-time; expires in 300s |
password_required | boolean | yes | V-REC password requiredtrue |
expires_in_seconds | integer | yes | Link expiry seconds300 |
display | string | yes | Display modeHOSTED_VREC_ONLY |
{
"url": "https://v-rec.example/card-reveal#one-time-token",
"expires_in_seconds": 300,
"password_required": true,
"display": "HOSTED_VREC_ONLY"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_denied / full_membership_required / merchant_membership_capability_denied / user_not_activeFormat: JSONNo scope, active FULL membership or spending capability
not_found / card_not_foundFormat: JSONUser or card not found
card_not_activeFormat: JSONCard status is not ACTIVE
empty_body_requiredFormat: JSONOnly empty body or {} is permitted
too_many_attemptsFormat: JSONUser challenge issuance rate exceeded
membership_capabilities_unavailable / secure_public_url_not_configuredFormat: JSONMembership capability data unavailable or HTTPS public origin not configured
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/matchmove-balance
Query live MatchMove wallet available balance
balances.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | User ID |
source | string | yes | SourceMATCHMOVE_LIVE |
currency | string | yes | CurrencyISO 4217 |
available | decimal string | yes | Issuer available balanceMatchMove direct |
observed_at | ISO 8601 | yes | Observed timeUTC |
is_live | boolean | yes | Live provider requesttrue |
{
"user_id": "usr_demo",
"source": "MATCHMOVE_LIVE",
"currency": "USD",
"available": "123.45",
"observed_at": "2026-10-22T12:00:00Z",
"is_live": true
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_deniedFormat: JSONNo permission
matchmove_balance_unavailableFormat: JSONIssuer balance unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/physical/link
Link a pre-issued physical card by Proxy/Association ID
cards.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | yes | Link idempotency keytrimmed non-empty, <=190; missing OR >190 both return idempotency_key_required |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
assoc_number | string | yes | Physical card Proxy / Association IDtrim -> remove ALL whitespace -> uppercase; then ^(?:PY)?\d{6,32}$; provider value normalized to PY + digits. Therefore raw input containing spaces may still be accepted. |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public IDusers.public_id; current generated IDs use usr_... |
card.id | string | yes | V-REC card public IDcards.public_id; card_... |
card.card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
card.brand | string|null | yes | Card brandcards.brand nullable |
card.masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
card.last4 | string|null | yes | Last four PAN digitsnullable |
card.currency | string | yes | Card currencycards.currency |
card.status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
card.atm_enabled | boolean | yes | ATM flag explicitly boolean-cast in mutation responsesexplicit (bool) cast by response cardShape(); key always present |
card.created_at | string | yes | Card record creation timecards.created_at NOT NULL DB datetime; key always present |
{
"user_id": "usr_demo",
"card": {
"id": "card_demo",
"card_type": "PHYSICAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": true,
"created_at": "2026-10-05 01:00:00"
}
}
invalid_jsonFormat: JSONMalformed JSON
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSON{id} is not an active/non-INACTIVE member of this Merchant
idempotency_key_required / assoc_number_required / invalid_assoc_number / kyc_not_approved / physical_link_requires_sakura_driver / sakura_cards_scope_required / idempotency_key_too_long / active_merchant_membership_required / vrec_instance_required / sakura_user_not_linkedFormat: JSONInput/provider validation
physical_card_link_unavailableFormat: JSONUpstream card-link unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/balances
Bulk-read balances (wallet information) of this Merchant's users
balances.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
currency | string | no | Balance currency3-letter code; default BASE_CURRENCY (USD); invalid => 422 invalid_currency |
limit | integer | no | Page size1–200, default 100 |
cursor | string | no | next_cursor from the previous responseinvalid => 422 invalid_cursor |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
merchant_id | string | yes | Merchant public ID |
currency | string | yes | Balance currency |
data[] | array<object> | yes | Per-user balances in ascending user order |
data[].user_id | string | yes | V-REC user public IDusr_... |
data[].beneficiary_id | string | yes | Beneficiary ID |
data[].membership_status | string | yes | Membership statusACTIVE etc. (INACTIVE is excluded) |
data[].balance_view_allowed | boolean | yes | Whether the balance may be viewed (can_view_balance). No amounts when false |
data[].balance_scope | string | no | TENANT_MERCHANT / SOURCE_MERCHANT |
data[].raw_balance / display_balance / held_balance / expired_locked / available | decimal string | no | Same values as GET /users/{id}/balance (TENANT_MERCHANT only) |
data[].read_only / allocatable / spendable / withdrawable | boolean | no | Same as GET /users/{id}/balance |
data[].reference_balance_via | string | no | For READONLY (reference balance from another Merchant) no amount is returned in bulk; fetch it individually from this path |
next_cursor | string|null | yes | Cursor for the next page (null when has_more=false) |
has_more | boolean | yes | Whether more items exist |
{
"merchant_id": "mrc_demo",
"currency": "USD",
"data": [
{
"user_id": "usr_demo",
"beneficiary_id": "VREC-ABC123",
"membership_status": "ACTIVE",
"currency": "USD",
"balance_view_allowed": true,
"balance_scope": "TENANT_MERCHANT",
"source_merchant_id": "MRC_DEMO",
"read_only": false,
"allocatable": true,
"spendable": true,
"withdrawable": true,
"raw_balance": "1500.00000000",
"display_balance": "1500.00000000",
"held_balance": "100.00000000",
"expired_locked": "0.00000000",
"available": "1400.00000000"
},
{
"user_id": "usr_ref",
"beneficiary_id": "VREC-DEF456",
"membership_status": "ACTIVE",
"currency": "USD",
"balance_view_allowed": true,
"balance_scope": "SOURCE_MERCHANT",
"source_merchant_id": "MRC_OTHER",
"read_only": true,
"allocatable": false,
"spendable": false,
"withdrawable": false,
"reference_balance_via": "/api/merchant/v1/users/usr_ref/balance"
},
{
"user_id": "usr_hidden",
"beneficiary_id": "VREC-GHI789",
"membership_status": "ACTIVE",
"currency": "USD",
"balance_view_allowed": false
}
],
"next_cursor": "MTIz",
"has_more": true
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks balances.read
invalid_currency / invalid_cursorFormat: JSONInvalid query value
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/balance
Get user balance scoped to this Merchant
balances.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
currency | string | no | Balance currencyomitted=>BASE_CURRENCY; if query key exists, trim+strtoupper only. An explicitly empty currency remains empty string; no ISO validation in current controller. |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public ID |
merchant_id | string | yes | Merchant public ID |
currency | string | yes | Balance currency |
balance_scope | string | yes | Balance scopeTENANT_MERCHANT (this Merchant) / SOURCE_MERCHANT (READONLY reference) |
source_merchant_id | string|null | yes | Source SAKURA Merchant of the user |
read_only | boolean | yes | Whether the membership is READONLY |
allocatable | boolean | yes | Whether tenant funds can be allocated |
spendable | boolean | yes | Whether the balance can be spent |
withdrawable | boolean | yes | Whether the balance can be withdrawn |
raw_balance | decimal-string | yes | Raw accounting balance; may be negative |
display_balance | decimal-string | yes | Display balance clamped at zero |
held_balance | decimal-string | yes | Authorization hold amount |
expired_locked | decimal-string | yes | Expired/locked balance |
available | decimal-string | yes | Available balancemax(display-held-expired, 0) |
{
"user_id": "usr_demo",
"merchant_id": "mrc_demo",
"currency": "USD",
"balance_scope": "TENANT_MERCHANT",
"source_merchant_id": "MRC_DEMO",
"read_only": false,
"allocatable": true,
"spendable": true,
"withdrawable": true,
"raw_balance": "1500.00000000",
"display_balance": "1500.00000000",
"held_balance": "100.00000000",
"expired_locked": "0.00000000",
"available": "1400.00000000"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSONUser outside Merchant boundary
balance_view_not_allowedFormat: JSONMembership has can_view_balance = false
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/transactions
List Merchant balance ledger entries
transactions.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
limit | integer | no | Maximum entriesPHP (int) cast then clamp 1..200; omitted=>50; abc/0/negative=>1; >200=>200 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[].public_id | string | yes | Merchant balance entry IDvbe_... |
data[].amount | decimal-string | yes | Amount |
data[].currency | string | yes | Currency |
data[].entry_type | string | yes | Entry type |
data[].reference_type | string | yes | Reference typeDB column NOT NULL; key always present |
data[].reference_id | string | yes | Reference IDDB column NOT NULL; key always present |
data[].metadata_json | string|null | yes | Metadata JSON stringkey always present; DB value nullable JSON text |
data[].created_at | string | yes | Created timestamp |
{
"data": [
{
"public_id": "vbe_demo",
"amount": "100.00",
"currency": "USD",
"entry_type": "CREDIT",
"reference_type": "PAYOUT",
"reference_id": "po_demo",
"metadata_json": "{}",
"created_at": "2026-10-05 01:00:00"
}
]
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
not_foundFormat: JSONUser outside Merchant boundary
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/payouts
List payouts attributed to this Merchant
payouts.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[].public_id | string | yes | Payout public IDpo_... |
data[].beneficiary_id | string | yes | Beneficiary ID |
data[].amount | decimal-string | yes | Payout amount |
data[].currency | string | yes | Currency |
data[].external_reference | string|null | yes | External referenceDB column nullable; key always present |
data[].purpose | string | yes | Purpose |
data[].status | string | yes | Payout status |
data[].created_at | string | yes | Created timestamp |
data[].credited_at | string|null | yes | Credited timestampkey always present; null until credited |
data[].user_id | string | yes | V-REC user public ID |
data[].email | string | yes | User email |
{
"data": [
{
"public_id": "po_demo",
"beneficiary_id": "VREC-DEMO",
"amount": "100.00",
"currency": "USD",
"external_reference": "INV-1001",
"purpose": "corporate_payout",
"status": "CREDITED",
"created_at": "2026-10-05 01:00:00",
"credited_at": "2026-10-05 01:01:00",
"user_id": "usr_demo",
"email": "[email protected]"
}
]
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/report
Get Merchant aggregate report
reports.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
currency | string | no | Report currencyomitted=>BASE_CURRENCY; trim+strtoupper only; explicitly empty remains empty string; current code does NOT validate ISO length/value. |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
merchant_id | string | yes | Merchant public ID |
currency | string | yes | Report currency |
raw_balance | decimal-string | yes | Raw balance total |
display_balance | decimal-string | yes | Display balance clamped at zero |
active_users | integer | yes | Number of active memberships |
payouts | integer | yes | Total payout count |
{
"merchant_id": "mrc_demo",
"currency": "USD",
"raw_balance": "15000.00",
"display_balance": "15000.00",
"active_users": 125,
"payouts": 310
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONHMAC/timestamp/nonce/credential/signature/replay/rate/IP authentication failure
merchant_scope_deniedFormat: JSONRequired scope is missing
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/wallet
Read a user's wallet balances (read-only)
wallets.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user idusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
skip_balances | any | no | Skip on-chain balance reads when present |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id / merchant_id | string | yes | V-REC user / merchant id |
read_only | boolean | yes | Always true; merchants cannot operate wallets via API |
has_wallet | boolean | yes | Whether the user has a wallet |
wallet | object|null | yes | address, version, network, chain_id, created_at |
balances | object|null | yes | Same shape as wallet_summary.balances |
gasless | object | yes | allowed, reason, daily_limit, daily_remaining |
transfer_blocked | boolean | yes | Suspended by operator |
{
"user_id": "usr_...",
"merchant_id": "mrc_demo",
"read_only": true,
"has_wallet": true,
"wallet": {
"address": "0x3f5c...",
"version": 1,
"network": "ethereum",
"chain_id": 1,
"created_at": "2026-10-16T01:00:00+00:00"
},
"balances": {
"usdt": {
"symbol": "USDT",
"contract": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"decimals": 6,
"balance": "120.500000",
"pending_incoming": "0.000000",
"available": "120.500000"
},
"eth": {
"symbol": "ETH",
"balance": "0.000000000000000000"
},
"complete": true,
"fetched_at": "2026-10-16T01:02:03+00:00"
},
"gasless": {
"allowed": true,
"reason": "merchant_managed",
"daily_limit": 5,
"daily_remaining": 4
},
"transfer_blocked": false
}
merchant_auth_required / merchant_signature_invalid / ...Format: JSONHMAC authentication failure
merchant_scope_denied / balance_view_not_allowedFormat: JSONMissing wallets.read / membership cannot view balances
not_found / wallet_user_not_foundFormat: JSONNot in this merchant / not linked on SAKURA
wallet_not_available / wallet_reference_unavailableFormat: JSONSAKURA crypto_wallet not granted / no source for READONLY membership
wallet_service_unavailableFormat: JSONTemporary failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/crypto-sales
Crypto sales, fees and royalties
crypto_sales.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
from / to | date | no | Completion date rangeYYYY-MM-DD |
limit | integer | no | Rows1..500 |
No body or no endpoint-specific response schema.
{
"merchant_id": "mrc_demo",
"totals": {
"sales_count": 2,
"gross_usd": "110.000000",
"company_fee_usd": "1.100000",
"merchant_royalty_usd": "0.550000"
},
"totals_basis": "returned_rows",
"data": [
{
"contract_no": "VCS-20261016-AB12CD34EF",
"contract_type": "SALE",
"status": "COMPLETED",
"product": "SGP_USD",
"product_name": "SGP-USD",
"product_quantity": "98.500000",
"seller": "SAKURA_EXCHANGE",
"payment": {
"asset": "USDT",
"symbol": "USDT",
"amount": "100.000000",
"unit_price_usd": "1.00000000",
"pay_to_address": "0x...",
"tx_hash": null
},
"pricing": {
"gross_usd": "100.000000",
"company_fee_rate": "0.01000000",
"company_fee_usd": "1.000000",
"merchant_royalty_rate": "0.00500000",
"merchant_royalty_usd": "0.500000",
"net_usd": "98.500000",
"points_per_usd": null
},
"merchant_responsibility": false,
"quote_expires_at": "2026-10-16T01:10:00+00:00",
"terms": {
"version": "2026-10-16",
"seller_name": "SAKURA EXCHANGE GUAM INC.",
"ja": [
"本契約は、購入者が暗号資産(USDT)を代金として、売主から「SGP-USD」を購入する売買契約です。通貨の両替ではありません。"
],
"en": [
"This is a sale contract …"
]
},
"replayed": false,
"user_id": "usr_demo"
}
]
}
merchant_auth_required / ...Format: JSONHMAC failure
merchant_scope_deniedFormat: JSONMissing crypto_sales.read
wallet_not_availableFormat: JSONSAKURA crypto_wallet not granted
crypto_sales_unavailableFormat: JSONTemporary failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/crypto-sales/funding-status
AML and manual prefund review status per merchant sale (read-only)
crypto_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
contract_no | string | no | Crypto sale contract number^VCS-[A-Za-z0-9-]{1,36}$ |
user_id | string | no | Merchant-owned V-REC public user IDusr_...; cannot combine with user_ref |
user_ref | string | no | SAKURA user reference^USR_[0-9]{1,16}$; cannot combine with user_id |
aml_status | enum | no | AML review statusPENDING|APPROVED|REJECTED |
prefund_status | enum | no | Prefund review statusPENDING|VERIFIED|REJECTED |
card_funding_allowed | enum | no | Card-funding-allowed flag0|1 |
cursor | string | no | Opaque cursor for next pagemax 256; Merchant-bound, expires in 1h; do not use after_id |
limit | integer | no | Page size1-100; default server limit |
No body or no endpoint-specific response schema.
{
"merchant_id": "mrc_demo",
"data": [
{
"contract_no": "VCS-DEMO",
"user_id": "usr_demo",
"aml_status": "APPROVED",
"prefund_status": "VERIFIED",
"card_funding_allowed": true
}
],
"has_more": false,
"next_cursor": null,
"funding_check_type": "MIXED_BY_ROW",
"note": "Prefund status is an operator review record, not proof of a MatchMove balance."
}
merchant_auth_failedFormat: JSONMerchant HMAC authentication failed
merchant_scope_denied / balance_view_not_allowedFormat: JSONcrypto_funding.read not granted, or balance visibility denied
not_foundFormat: JSONRequested user_id not owned by merchant
funding_status_not_enabled / funding_status_unavailable / user_sakura_link_unavailableFormat: JSONSAKURA Connected Apps scope or linking not available
invalid_cursor / ambiguous_user_filter / invalid_...Format: JSONInvalid filter or cursor
funding_capabilities_unavailable / funding_status_unavailableFormat: JSONRequired membership schema or connected service unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/schema
Fetch current SAKURA KYCFORM
kyc.preload
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC public user IDusr_... |
No body or no endpoint-specific response schema.
{
"user_id": "usr_demo",
"form": {
"fields": []
}
}
merchant_auth_required / merchant_signature_invalidFormat: JSONHMAC authentication failed
merchant_scope_deniedFormat: JSONMissing explicit scope
sakura_unavailableFormat: JSONSAKURA upstream failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/preload
Preload KYC data and evidence
kyc.preload
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | yes | KYC submission idempotency key8..190 chars; raw JSON same on retry |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC public user IDusr_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
fields | object | yes | SAKURA KYCFORM scalar field valuesmax 100 |
documents.<field>.data_base64 | string | yes | Up to 6 base64 imagesJPEG/PNG, <=5MiB each, <=10MiB total |
consent.confirmed | boolean | yes | Explicit user consenttrue |
consent.obtained_at | date-time | yes | Consent timestamppast 30d |
consent.evidence_ref | string | yes | Evidence reference8..190 chars |
consent.terms_version | string | yes | Consent terms version |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user ID |
draft_saved | boolean | yes | Preload complete; false blocks link |
kyc_status | string | yes | KYC state (not necessarily APPROVED) |
next_action | string|object|null | no | Next action |
{
"user_id": "usr_demo",
"draft_saved": true,
"kyc_status": "PENDING",
"next_action": {
"type": "ready_for_ekyc"
}
}
merchant_auth_required / merchant_signature_invalidFormat: JSONHMAC authentication failed
merchant_scope_deniedFormat: JSONMissing explicit scope
sakura_unavailableFormat: JSONSAKURA upstream failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/kyc/onboarding-link
Retrieve SAKURA eKYC link for verified cardholder delivery
kyc.link.issue
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC public user IDusr_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user ID |
ekyc_url | string|null | conditional | Private eKYC short link for cardholder |
kyc_status | string | yes | KYC status |
next_action | string|object|null | no | Next action |
{
"user_id": "usr_demo",
"ekyc_url": "https://sakura.example/kyc/EXAMPLE",
"kyc_status": "PENDING",
"next_action": "send_link_to_user"
}
merchant_auth_required / merchant_signature_invalidFormat: JSONHMAC authentication failed
merchant_scope_deniedFormat: JSONMissing explicit scope
sakura_unavailableFormat: JSONSAKURA upstream failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/users/{id}/cards/{cardId}/issuer-status
Check live MatchMove card status
cards.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
cardId | string | yes | V-REC card public IDcard_... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | User public IDusr_... |
card_id | string | yes | Card public IDcard_... |
issuer_status | string | yes | Live issuer statusACTIVE|FROZEN|CLOSED |
source | string | yes | SourceMATCHMOVE_LIVE |
observed_at | ISO 8601 | yes | Observed timeUTC |
{
"user_id": "usr_demo",
"card_id": "card_demo",
"issuer_status": "FROZEN",
"source": "MATCHMOVE_LIVE",
"observed_at": "2026-10-23T00:00:00Z"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_ip_not_allowedFormat: JSONMerchant authentication, signature, timestamp, nonce or IP validation failed
merchant_scope_deniedFormat: JSONNo permission
card_not_foundFormat: JSONCard not found
issuer_card_status_unavailableFormat: JSONIssuer status cannot be verified
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
https://api.v-rec.xyz
/api/merchant/v1/tenant-funding/balance
Read SAKURA-authoritative Tenant balance and expiry policy
tenant_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
merchant_id | string | yes | V-REC Merchant public ID bound to the authenticated credentialmrc_... |
tenant_funding.tenant_ref | string | yes | SAKURA Tenant Account referenceVTA_... |
tenant_funding.currency | string | yes | Tenant funding currencyUSD |
tenant_funding.source_asset | string | yes | Backing asset on SAKURAcurrent implementation: SGP-USD |
tenant_funding.commitment | decimal string | yes | Tenant accounting commitmentSAKURA-authoritative |
tenant_funding.reserved | decimal string | yes | Accounting amount currently reservedSAKURA-authoritative |
tenant_funding.available | decimal string | yes | Accounting amount currently available for new allocationSAKURA-authoritative; execution re-checks |
tenant_funding.funded | decimal string | yes | Cumulative funded principalSAKURA-authoritative |
tenant_funding.returned | decimal string | yes | Cumulative returned principalSAKURA-authoritative |
tenant_funding.fees | decimal string | yes | Cumulative Tenant Funding feesSAKURA-authoritative |
tenant_funding.funding_fee_fixed | decimal string | yes | Fixed fee charged per allocation. An allocation needs amount + this fee to be within availableSAKURA-authoritative; 0 when no fee is configured |
tenant_funding.expiry_policy | string | yes | Current effective funding expiry policye.g. EXPIRABLE_180 / NO_EXPIRY |
tenant_funding.expiry_days | integer|null | yes | Expiry days; may be null for no-expiry policytypically 180 when EXPIRABLE_180 |
tenant_funding.status | string | yes | SAKURA Tenant statusexpected ACTIVE for new funding |
tenant_funding.mode | string | yes | SAKURA Tenant Funding modeENFORCED required for new funding |
tenant_funding.reconciliation_status | string | yes | SAKURA reconciliation stateSUSPENDED_RECONCILIATION blocks new movement |
tenant_funding.last_reconciled_at | string|null | yes | Last reconciliation timeUTC/ISO-style value from SAKURA |
{
"merchant_id": "mrc_demo",
"tenant_funding": {
"tenant_ref": "VTA_12345678",
"currency": "USD",
"source_asset": "SGP-USD",
"commitment": "15000.00000000",
"reserved": "0.00000000",
"available": "12497.50000000",
"funded": "2500.00000000",
"returned": "0.00000000",
"fees": "2.50000000",
"funding_fee_fixed": "2.50000000",
"expiry_policy": "EXPIRABLE_180",
"expiry_days": 180,
"status": "ACTIVE",
"mode": "ENFORCED",
"reconciliation_status": "MATCHED",
"last_reconciled_at": "2026-10-05T06:00:00Z"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
tenant_binding_not_foundFormat: JSONNo Tenant Funding binding exists for the authenticated Merchant
sakura_not_connected / tenant_not_found / tenant_funding_scope_not_granted / <upstream error>Format: JSONSAKURA Connected App or authoritative Tenant Funding state is unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/diagnostics
Diagnose Tenant Funding operational readiness without moving funds
tenant_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
user_id | string | no | Optional V-REC user public ID for membership/allocation preflightmust belong to authenticated Merchant; omitted => Tenant-wide diagnostics only |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
merchant_id | string | yes | Authenticated Merchant public IDmrc_... |
diagnostics.schema.tenant_funding | boolean | yes | Tenant Funding schema is installedread-only check |
diagnostics.schema.membership_capabilities | boolean | yes | FULL/READONLY capability schema is installedread-only check |
diagnostics.schema.direct_allocation | boolean | yes | v2.4.2 direct-allocation schema is installedread-only check |
diagnostics.binding | object|null | yes | Tenant binding for the current Merchanttenant_ref/local_mode/tenant_lots_withdrawable |
diagnostics.sakura.reachable | boolean | yes | Whether SAKURA authoritative Tenant Funding state was reachableno fund movement |
diagnostics.sakura.status | object|null | yes | SAKURA Tenant management/status resultread-only |
diagnostics.sakura.balance | object|null | yes | SAKURA-authoritative Tenant balanceread-only |
diagnostics.operations | object | yes | Current readiness for balance/capacity/allocate/reverse/return operationsdiagnostic flags only |
diagnostics.user | object|null | yes | Membership/source-target Merchant/KYC/SAKURA-link/capacity information when user_id is suppliedonly a user inside the authenticated Merchant boundary is returned |
{
"merchant_id": "mrc_demo",
"diagnostics": {
"schema": {
"tenant_funding": true,
"membership_capabilities": true,
"direct_allocation": true
},
"binding": {
"tenant_ref": "VTA_12345678",
"local_mode": "ENFORCED",
"tenant_lots_withdrawable": true
},
"sakura": {
"reachable": true,
"status": {
"management_enabled": true,
"api_execute_enabled": true
},
"balance": {
"available": "12497.50",
"expiry_policy": "EXPIRABLE_180"
},
"error": null
},
"user": {
"public_id": "usr_demo",
"found": true,
"kyc_status": "APPROVED",
"membership_status": "ACTIVE",
"access_mode": "FULL",
"source_sakura_merchant_ref": "MRC_A",
"target_sakura_merchant_ref": "MRC_A",
"sakura_user_link_present": true,
"can_receive_tenant_funds": true,
"capacity": {
"allocatable_amount": "12497.50"
}
},
"operations": {
"balance_read": true,
"capacity_check": true,
"allocate": true,
"allocation_status_read": true,
"allocation_reverse": true,
"user_return": false
}
}
}
merchant_auth_required / merchant_signature_invalid / ...Format: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONtenant_funding.read scope is missing
tenant_funding_diagnostics_failedFormat: JSONDiagnostics could not be produced due to a transient failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/users/{id}/capacity
Check whether/how much Tenant funding can be allocated to a user (indicative maximum)
tenant_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | path string | yes | V-REC user public IDusr_...; membership must belong to authenticated Merchant |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
currency | string | no | Capacity currencydefault USD; current implementation accepts USD only |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | Target V-REC user public IDusr_... |
merchant_id | string | yes | Authenticated Merchant public IDmrc_... |
eligible | boolean | yes | Whether this membership is eligible to receive Tenant fundssuccessful response is true |
currency | string | yes | Capacity currencyUSD |
allocatable_amount | decimal string | yes | Indicative maximum you can allocate now (tenant available − allocation fee, floored to cents)non-reserving preflight; execution re-checks SAKURA and the Merchant MatchMove wallet |
tenant_available | decimal string | yes | Tenant available amount (SAKURA-authoritative)same source as balance.available |
funding_fee | decimal string | yes | Fixed fee per allocation0 when no fee is configured |
user_tenant_origin_remaining | decimal string | yes | User's remaining unspent principal originating from this TenantV-REC provenance ledger |
tenant_ref | string | yes | Tenant referenceVTA_... |
access_mode | string | yes | Membership access modeFULL on successful capacity response |
expiry_policy | string|null | yes | Current expiry policy expected for new allocationfinal snapshot is returned by SAKURA on execution |
expiry_days | integer|null | yes | Current expiry dayscommonly 180 for EXPIRABLE_180 |
reconciliation_status | string|null | yes | Tenant reconciliation statenew movement requires healthy state |
{
"user_id": "usr_demo",
"merchant_id": "mrc_demo",
"eligible": true,
"currency": "USD",
"allocatable_amount": "12495.00",
"tenant_available": "12497.50000000",
"funding_fee": "2.50000000",
"user_tenant_origin_remaining": "500.00000000",
"tenant_ref": "VTA_12345678",
"access_mode": "FULL",
"expiry_policy": "EXPIRABLE_180",
"expiry_days": 180,
"reconciliation_status": "MATCHED"
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
not_foundFormat: JSONUser is not a membership of this Merchant
tenant_funding_not_allowed_for_membership / cross_merchant_tenant_funding_not_allowedFormat: JSONREADONLY, can_receive_tenant_funds=false, or source Merchant differs from the Tenant Merchant
tenant_funding_usd_only / tenant_membership_not_activeFormat: JSONNon-USD request or membership is not ACTIVE
membership_capabilities_not_installed / tenant_funding_not_enabled_in_sakura / <upstream error>Format: JSONRequired migration is missing, SAKURA Tenant Funding is disabled, or authoritative state is unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/allocations
Allocate Tenant funds directly to a V-REC user
tenant_funding.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | yes | Idempotency key for the logical allocationtrimmed non-empty; max 190 chars; reuse with different user/amount => 409 |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | Target V-REC user public IDusr_...; ACTIVE/FULL same-source Merchant membership required |
amount | decimal string | yes | Allocation principalpositive USD; exact cent precision |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
allocation.id | string | yes | V-REC Tenant Allocation operation IDvta_... |
allocation.user_id | string | yes | Target V-REC user public IDusr_... |
allocation.amount | decimal string | yes | Requested principalpositive USD amount with cent precision |
allocation.currency | string | yes | CurrencyUSD |
allocation.status | string | yes | Allocation state. COMPLETED = credited; REJECTED = not executed, no funds moved (final); FAILED/MISMATCH = needs review (final); SUBMITTING/UNKNOWN/RECONCILE_REQUIRED = not final (retry with the same Idempotency-Key)SUBMITTING / UNKNOWN / RECONCILE_REQUIRED / COMPLETED / REJECTED / FAILED / MISMATCH |
allocation.error_code | string|null | yes | Machine-readable reason code (e.g. why REJECTED). Examples: insufficient_tenant_available, insufficient_merchant_physicalnull when not applicable |
allocation.sakura_request_uuid | string|null | yes | SAKURA funding request UUIDpresent after upstream acknowledgement |
allocation.error | string|null | yes | Diagnostic code/summary for non-success statesdoes not expose secrets |
allocation.created_at | string|null | yes | Operation creation timeUTC database timestamp |
allocation.completed_at | string|null | yes | Completion timenull until completed |
{
"allocation": {
"id": "vta_demo",
"user_id": "usr_demo",
"amount": "100.00000000",
"currency": "USD",
"status": "COMPLETED",
"error_code": null,
"sakura_request_uuid": "01J...",
"error": null,
"created_at": "2026-10-05 07:00:00",
"completed_at": "2026-10-05 07:00:01"
}
}
{
"error": "insufficient_tenant_available",
"allocation": {
"id": "vta_demo2",
"user_id": "usr_demo",
"amount": "50000.00000000",
"currency": "USD",
"status": "REJECTED",
"error_code": "insufficient_tenant_available",
"sakura_request_uuid": null,
"error": "insufficient_tenant_available: Tenant available is insufficient. available=12497.50, required=50002.50",
"created_at": "2026-10-05 07:00:00",
"completed_at": null
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
invalid_jsonFormat: JSONRequest body is not a JSON object
not_foundFormat: JSONTarget user is not part of this Merchant
idempotency_key_required / user_id_and_amount_required / invalid_amount / kyc_not_approved / tenant_membership_not_active / sakura_user_link_missing / tenant_funding_not_installedFormat: JSONRequired fields, amount, KYC, membership, SAKURA link or migration prerequisite is not satisfied
tenant_funding_not_allowed_for_membership / cross_merchant_tenant_funding_not_allowedFormat: JSONNew fund movement to a READONLY or cross-Merchant membership
insufficient_tenant_available / insufficient_merchant_physical / tenant_funding_not_enabled / user_mm_missing / merchant_mm_missing / user_not_in_tenant_program / idempotency_key_reused / operation_in_progress / unresolved_request_exists / tenant_allocation_rejectedFormat: JSONRejected without moving funds (final). Typical reasons: amount + fee exceeds tenant available, Merchant MatchMove wallet short, Tenant Funding disabled, Idempotency-Key reused with different parameters, or another money operation for the same user in progress (operation_in_progress / unresolved_request_exists). REJECTED moved no money; fix the cause and retry with a new Idempotency-Key. The body contains error and allocation (status=REJECTED, error_code)
membership_capabilities_not_installed / tenant_allocation_api_not_installed / tenant_funding_not_enabled_in_sakura / tenant_funding_internal_error / <upstream error>Format: JSONTenant Funding disabled, ambiguous SAKURA/MatchMove outcome, or transient internal failure
(allocation.status = SUBMITTING / UNKNOWN / RECONCILE_REQUIRED)Format: JSONOutcome not final (temporary conflict, network). Funds may have moved: do not create a new operation; resend with the same Idempotency-Key or confirm via the list/detail endpoints
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/allocations
List Tenant Allocation (TopUp) results updated since a given time
tenant_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
updated_since | ISO-8601 string | no | Return only allocations updated at or after this timee.g. 2026-10-15T00:00:00+09:00; without offset = UTC; invalid => 422 invalid_updated_since |
status | string | no | Filter by status (comma-separated)SUBMITTING / UNKNOWN / RECONCILE_REQUIRED / COMPLETED / REJECTED / FAILED / MISMATCH; other => 422 invalid_status |
user_id | string | no | Filter by userusr_...; not in this Merchant => 404 |
limit | integer | no | Page size1–200, default 100 |
cursor | string | no | next_cursor from the previous responseinvalid => 422 invalid_cursor |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array<object> | yes | Allocations; each item has the allocation fields plus updated_at |
data[].id | string | yes | Allocation IDvta_... |
data[].user_id | string | yes | Target V-REC user public IDusr_... |
data[].amount | decimal string | yes | Requested principal |
data[].currency | string | yes | CurrencyUSD |
data[].status | string | yes | Allocation statesee allocation.status |
data[].error_code | string|null | yes | Reason code |
data[].sakura_request_uuid | string|null | yes | SAKURA funding request UUID |
data[].error | string|null | yes | Diagnostic message |
data[].created_at | string | yes | Creation timeUTC 'YYYY-MM-DD HH:MM:SS' |
data[].completed_at | string|null | yes | Completion time |
data[].updated_at | string | yes | Last update time; changes whenever the result changesUTC 'YYYY-MM-DD HH:MM:SS' |
next_cursor | string|null | yes | Cursor for the next pagepass as cursor; null when there is no item |
has_more | boolean | yes | Whether more items exist |
{
"data": [
{
"id": "vta_demo2",
"user_id": "usr_demo",
"amount": "50000.00000000",
"currency": "USD",
"status": "REJECTED",
"error_code": "insufficient_tenant_available",
"sakura_request_uuid": null,
"error": "insufficient_tenant_available: Tenant available is insufficient. available=12497.50, required=50002.50",
"created_at": "2026-10-15 01:00:00",
"completed_at": null,
"updated_at": "2026-10-15 01:00:01"
},
{
"id": "vta_demo",
"user_id": "usr_demo",
"amount": "100.00000000",
"currency": "USD",
"status": "COMPLETED",
"error_code": null,
"sakura_request_uuid": "TF_01J...",
"error": null,
"created_at": "2026-10-15 01:05:00",
"completed_at": "2026-10-15 01:05:01",
"updated_at": "2026-10-15 01:05:01"
}
],
"next_cursor": "MjAyNi0xMC0xNSAwMTowNTowMXw0Mg",
"has_more": false
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks tenant_funding.read
not_foundFormat: JSONuser_id is not a member of this Merchant
invalid_updated_since / invalid_status / invalid_cursorFormat: JSONInvalid query value
tenant_allocation_api_not_installedFormat: JSONv2.4.2 allocation migration has not been applied
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/allocations/{id}
Get a Tenant Allocation operation
tenant_funding.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | path string | yes | Allocation operation IDvta_...; must belong to authenticated Merchant |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
allocation.id | string | yes | V-REC Tenant Allocation operation IDvta_... |
allocation.user_id | string | yes | Target V-REC user public IDusr_... |
allocation.amount | decimal string | yes | Requested principalpositive USD amount with cent precision |
allocation.currency | string | yes | CurrencyUSD |
allocation.status | string | yes | Allocation state. COMPLETED = credited; REJECTED = not executed, no funds moved (final); FAILED/MISMATCH = needs review (final); SUBMITTING/UNKNOWN/RECONCILE_REQUIRED = not final (retry with the same Idempotency-Key)SUBMITTING / UNKNOWN / RECONCILE_REQUIRED / COMPLETED / REJECTED / FAILED / MISMATCH |
allocation.error_code | string|null | yes | Machine-readable reason code (e.g. why REJECTED). Examples: insufficient_tenant_available, insufficient_merchant_physicalnull when not applicable |
allocation.sakura_request_uuid | string|null | yes | SAKURA funding request UUIDpresent after upstream acknowledgement |
allocation.error | string|null | yes | Diagnostic code/summary for non-success statesdoes not expose secrets |
allocation.created_at | string|null | yes | Operation creation timeUTC database timestamp |
allocation.completed_at | string|null | yes | Completion timenull until completed |
{
"allocation": {
"id": "vta_demo",
"user_id": "usr_demo",
"amount": "100.00000000",
"currency": "USD",
"status": "COMPLETED",
"error_code": null,
"sakura_request_uuid": "01J...",
"error": null,
"created_at": "2026-10-05 07:00:00",
"completed_at": "2026-10-05 07:00:01"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
tenant_allocation_not_foundFormat: JSONAllocation does not exist or belongs to another Merchant
tenant_allocation_api_not_installedFormat: JSONv2.4.2 allocation migration has not been applied
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/allocations/{id}/reverse
Return only unspent principal originating from one specific allocation
tenant_funding.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | yes | Idempotency key for this reversaltrimmed non-empty; max 190 chars |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | path string | yes | Allocation operation ID to reversevta_...; must be COMPLETED |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
amount | decimal string | yes | Amount to returnpositive USD; exact cent precision; cannot exceed this allocation's remaining lot principal |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
return.id | string | yes | V-REC Tenant Return operation IDvtr_... |
return.amount | decimal string | yes | Principal returned to the Tenantpositive USD amount with cent precision |
return.currency | string | yes | CurrencyUSD |
return.status | string | yes | Return stateRESERVED / SUBMITTED / RECONCILE_REQUIRED / FAILED / COMPLETED |
return.sakura_request_uuid | string|null | yes | SAKURA return/funding request UUIDmay be null before acknowledgement |
return.error | string|null | yes | Diagnostic code/summary for non-success states |
return.created_at | string|null | yes | Operation creation timeUTC database timestamp |
return.updated_at | string|null | yes | Last update timeUTC database timestamp |
{
"return": {
"id": "vtr_demo",
"amount": "25.00000000",
"currency": "USD",
"status": "COMPLETED",
"sakura_request_uuid": "01J...",
"error": null,
"created_at": "2026-10-05 07:10:00",
"updated_at": "2026-10-05 07:10:01"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
invalid_jsonFormat: JSONRequest body is not a JSON object
tenant_allocation_not_foundFormat: JSONAllocation does not exist or belongs to another Merchant
idempotency_key_required / amount_required / invalid_amount / tenant_allocation_not_reversibleFormat: JSONMissing input, invalid amount, or allocation is not COMPLETED/reversible
reverse_amount_exceeds_remaining / tenant_return_in_progress / idempotency_key_reused / insufficient_tenant_origin_fundsFormat: JSONRequested reverse exceeds this allocation's remaining principal, another return is in progress, or idempotency conflicts
<upstream error>Format: JSONSAKURA outcome cannot be determined temporarily
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/tenant-funding/users/{id}/return
Return this Tenant's remaining user-held principal to the Tenant
tenant_funding.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | yes | Idempotency key for the logical returntrimmed non-empty; max 190 chars |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | path string | yes | Target V-REC user public IDusr_...; membership must belong to authenticated Merchant |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
amount | decimal string | yes | Return amountpositive USD; exact cent precision; limited to this Tenant's remaining origin lots |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
return.id | string | yes | V-REC Tenant Return operation IDvtr_... |
return.amount | decimal string | yes | Principal returned to the Tenantpositive USD amount with cent precision |
return.currency | string | yes | CurrencyUSD |
return.status | string | yes | Return stateRESERVED / SUBMITTED / RECONCILE_REQUIRED / FAILED / COMPLETED |
return.sakura_request_uuid | string|null | yes | SAKURA return/funding request UUIDmay be null before acknowledgement |
return.error | string|null | yes | Diagnostic code/summary for non-success states |
return.created_at | string|null | yes | Operation creation timeUTC database timestamp |
return.updated_at | string|null | yes | Last update timeUTC database timestamp |
{
"return": {
"id": "vtr_demo",
"amount": "25.00000000",
"currency": "USD",
"status": "COMPLETED",
"sakura_request_uuid": "01J...",
"error": null,
"created_at": "2026-10-05 07:10:00",
"updated_at": "2026-10-05 07:10:01"
}
}
merchant_auth_required / merchant_timestamp_invalid / merchant_nonce_invalid / merchant_credential_invalid / merchant_signature_invalid / merchant_replay / merchant_rate_limited / merchant_ip_not_allowedFormat: JSONMerchant HMAC authentication failed
merchant_scope_deniedFormat: JSONCredential lacks the required tenant_funding scope
invalid_jsonFormat: JSONRequest body is not a JSON object
not_foundFormat: JSONTarget user is not part of this Merchant
idempotency_key_required / amount_required / invalid_amount / sakura_user_link_missingFormat: JSONRequired input, amount, binding or SAKURA link prerequisite is not satisfied
insufficient_tenant_origin_funds / tenant_return_in_progress / idempotency_key_reusedFormat: JSONInsufficient remaining principal originating from this Tenant, another return is in progress, or idempotency conflict
tenant_funding_not_enforced / <upstream error>Format: JSONSAKURA outcome cannot be determined temporarily
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
https://v-rec.xyz
/api/v1/me
Get authenticated user profile summary
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC user public IDusr_... |
beneficiary_id | string | yes | Beneficiary ID |
email | string | yes | Current SAKURA email with local fallbackemail |
name | string | yes | Current SAKURA name; empty string when absent |
country | string|null | yes | SAKURA profile.country_codekey always present; nullable |
kyc_status | string | yes | KYC status from SAKURA with local fallback |
{
"id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"email": "[email protected]",
"name": "Taro Yamada",
"country": "JP",
"kyc_status": "APPROVED"
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headersakura_profile_unavailableFormat: JSONSAKURA profile unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/kyc
Synchronize authenticated user KYC status
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
status | string | yes | Normalized KYC statusNOT_STARTED|PENDING|APPROVED|REJECTED|REVIEW|CORRECTION_REQUIRED |
checked_at | string | yes | Last sync timestamp updated by this callDB UTC datetime |
{
"status": "APPROVED",
"checked_at": "2026-10-05 01:00:00"
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerkyc_service_unavailableFormat: JSONSanitized upstream KYC conflict
kyc_service_unavailableFormat: JSONSanitized upstream KYC validation state
kyc_service_unavailableFormat: JSONUpstream KYC rate limit
kyc_service_unavailableFormat: JSONKYC service unavailable; correlation_id is returned
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/kyc/form
Fetch the live SAKURA KYCFORM definition
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_ref | string | conditional | SAKURA user ref added by Connected Apps bridgepresent in current connected_apps path; mock test driver omits |
kyc_status | string | yes | SAKURA KYC statuscurrent SAKURA adapter: NOT_STARTED|PENDING|APPROVED|CORRECTION_REQUIRED |
editable | boolean | yes | Whether form is editable |
fields[] | array<object> | yes | SAKURA KYCFORM plus profile prerequisites not already defined in KYCFORM |
fields[].name | string | yes | Field name to submit |
fields[].label | string | yes | Display label |
fields[].type | integer | yes | SAKURA field type2=file, 3=textarea handling, 4=date handling; other values treated as string/select as configured |
fields[].required | boolean | yes | Required flag |
fields[].control | string | yes | Recommended UI controlfile|select|textarea|date|text |
fields[].value | mixed|null | yes | Current value; file fields always nullkey always present |
fields[].has_file | boolean | yes | Whether an existing file is stored |
fields[].options | array<object> | yes | Candidate list; may be empty for non-select fields |
fields[].options[].code | string | yes | Candidate code to submit |
fields[].options[].label | string | yes | Candidate label |
fields[].section | string | yes | Definition sectionkyc_form|profile |
ekyc | object | conditional | eKYC public state in Connected Apps pathmock test driver omits |
ekyc.state | string | yes | SAKURA eKYC stateverified|link_ready|link_refresh_required|provider_processing|link_missing_recovery|issue_failed_editable|rejected|issue_required|not_started |
ekyc.verified | boolean | yes | Verified flag |
ekyc.has_history | boolean | yes | Whether eKYC history exists |
ekyc.has_link | boolean | yes | Whether an eKYC link exists |
ekyc.has_active_link | boolean | yes | Backward-compatible alias of has_link |
ekyc.provider_session_fresh | boolean | yes | Whether provider session is fresh |
ekyc.action_url | string|null | yes | SAKURA stable short URL; end-user session onlysensitive bearer-style URL; nullable |
ekyc.expires_at | string|null | yes | eKYC link expiryISO 8601 or null |
ekyc.editable | boolean | yes | Whether KYC is currently editable |
ekyc.can_issue | boolean | yes | Whether eKYC can be issued/reissued |
ekyc.can_open | boolean | yes | Whether current link can be opened |
next_action.type | string | yes | Next client actionredirect|completed|wait|correct|retry |
next_action.url | string | conditional | SAKURA short URL only when type=redirectomitted otherwise |
next_action.retry_after_seconds | integer | conditional | Only when type=waitcurrently 5 |
{
"user_ref": "USR_00000001",
"kyc_status": "NOT_STARTED",
"editable": true,
"fields": [
{
"name": "mobile_country_code",
"label": "Country code",
"type": 1,
"required": true,
"control": "select",
"value": "+81",
"has_file": false,
"options": [
{
"code": "+81",
"label": "JPN (+81)"
}
],
"section": "kyc_form"
}
],
"ekyc": {
"state": "not_started",
"verified": false,
"has_history": false,
"has_link": false,
"has_active_link": false,
"provider_session_fresh": false,
"action_url": null,
"expires_at": null,
"editable": true,
"can_issue": false,
"can_open": false
},
"next_action": {
"type": "correct"
}
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerkyc_service_unavailableFormat: JSONUpstream conflict
kyc_service_unavailableFormat: JSONUpstream validation state
kyc_service_unavailableFormat: JSONUpstream rate limit
kyc_service_unavailableFormat: JSONTemporary service failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/kyc/submit
Submit the dynamic KYCFORM in the authenticated user session
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
<fields[].name> | string|file | dynamic | Only names returned by GET /api/v1/me/kyc/form are acceptedunknown fields => 422 validation_error |
<select field> | string | dynamic | For control=select submit options[].code onlyout-of-catalog => 422 validation_error; strict catalogs fail closed if unavailable |
<file field> | file | dynamic | Type=2 file fieldimage; png/jpg/jpeg; max 5120 KB; required only if required=true and no existing file |
<textarea field> | string | dynamic | Type=3 textmax 5000 when no option catalog |
<date field> | string | dynamic | Type=4 dateKYCFORM: valid date; profile prerequisite: Y-m-d; birthday before today |
<other string field> | string | dynamic | Other string fieldsmax 255 when no option catalog |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_ref | string | conditional | SAKURA user ref added in Connected Apps pathmock test driver omits |
success | boolean | yes | eKYC issue result |
kyc_status | string | yes | KYC status after submit |
editable | boolean | yes | Editability after submit |
error | string | conditional | Only when success=falseekyc_issue_failed |
reason_code | string | conditional | Allow-listed reason code when success=falseidentity_mobile_already_registered|identity_email_already_registered|identity_already_registered|provider_session_unavailable |
reason_field | string|null | conditional | Related field on failure; nullable |
ekyc | object | conditional | eKYC public state in Connected Apps pathmock test driver omits |
ekyc.state | string | yes | SAKURA eKYC stateverified|link_ready|link_refresh_required|provider_processing|link_missing_recovery|issue_failed_editable|rejected|issue_required|not_started |
ekyc.verified | boolean | yes | Verified flag |
ekyc.has_history | boolean | yes | Whether eKYC history exists |
ekyc.has_link | boolean | yes | Whether an eKYC link exists |
ekyc.has_active_link | boolean | yes | Backward-compatible alias of has_link |
ekyc.provider_session_fresh | boolean | yes | Whether provider session is fresh |
ekyc.action_url | string|null | yes | SAKURA stable short URL; end-user session onlysensitive bearer-style URL; nullable |
ekyc.expires_at | string|null | yes | eKYC link expiryISO 8601 or null |
ekyc.editable | boolean | yes | Whether KYC is currently editable |
ekyc.can_issue | boolean | yes | Whether eKYC can be issued/reissued |
ekyc.can_open | boolean | yes | Whether current link can be opened |
next_action.type | string | yes | Next client actionredirect|completed|wait|correct|retry |
next_action.url | string | conditional | SAKURA short URL only when type=redirectomitted otherwise |
next_action.retry_after_seconds | integer | conditional | Only when type=waitcurrently 5 |
{
"user_ref": "USR_00000001",
"success": true,
"kyc_status": "PENDING",
"editable": false,
"ekyc": {
"state": "link_ready",
"verified": false,
"has_history": true,
"has_link": true,
"has_active_link": true,
"provider_session_fresh": true,
"action_url": "https://sakura.example/ekyc/short-token",
"expires_at": "2026-10-05T01:15:00+00:00",
"editable": false,
"can_issue": false,
"can_open": true
},
"next_action": {
"type": "redirect",
"url": "https://sakura.example/ekyc/short-token"
}
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf. Security::requireCsrf() runs before Auth::requireUser().
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headervalidation_errorFormat: JSONPer-field validation errors with errors map and correlation_id
kyc_service_unavailableFormat: JSONConflict such as concurrent processing
kyc_service_unavailableFormat: JSONUpstream rate limit
kyc_service_unavailableFormat: JSONTemporary KYC service failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/balance
Get user V-REC and per-Merchant balances
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
scope_instance_id | integer | yes | Current V-REC instance internal IDauthenticated Auth::user requires a positive active instance id |
vrec_raw | decimal-string | yes | Raw V-REC balance |
vrec_available | decimal-string | yes | Spendable balance |
vrec_withdrawable | decimal-string | yes | Withdrawable balance |
authorization_holds | decimal-string | yes | Authorization holds |
expired_locked | decimal-string | yes | Expired locked balance when instance existspresent on authenticated path |
merchant_balances[] | array<object> | yes | Per-merchant balances |
merchant_balances[].merchant_id | integer | yes | Internal merchant ID |
merchant_balances[].merchant_public_id | string | yes | Merchant public ID |
merchant_balances[].display_name | string | yes | Merchant display name |
merchant_balances[].raw_balance | decimal-string | yes | Raw balance |
merchant_balances[].display_balance | decimal-string | yes | Display balance |
merchant_balances[].expired_locked | decimal-string | conditional | Expired locked amount |
merchant_balances[].available | decimal-string | yes | Available; zero when membership/merchant is inactive |
merchant_balances[].membership_status | string | yes | Membership status |
merchant_balances[].merchant_status | string | yes | Merchant status |
{
"scope_instance_id": 1,
"vrec_raw": "1500.00",
"vrec_available": "1400.00",
"vrec_withdrawable": "1300.00",
"authorization_holds": "100.00",
"expired_locked": "0.00",
"merchant_balances": [
{
"merchant_id": 10,
"merchant_public_id": "mrc_demo",
"display_name": "Example Merchant",
"raw_balance": "1500.00",
"membership_status": "ACTIVE",
"merchant_status": "ACTIVE",
"display_balance": "1500.00",
"expired_locked": "0.00",
"available": "1500.00"
}
]
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/payouts
List authenticated user payouts
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[].id | string | yes | Payout ID |
data[].amount | decimal-string | yes | Amount |
data[].currency | string | yes | Currency |
data[].external_reference | string|null | yes | External referenceDB column nullable; key always present |
data[].purpose | string | yes | Purpose |
data[].status | string | yes | Status |
data[].created_at | string | yes | Created timestamp |
data[].credited_at | string|null | yes | Credited timestampkey always present; null until credited |
data[].company | string | yes | Partner/company display name |
{
"data": [
{
"id": "po_demo",
"amount": "100.00",
"currency": "USD",
"external_reference": "INV-1001",
"purpose": "corporate_payout",
"status": "CREDITED",
"created_at": "2026-10-05 01:00:00",
"credited_at": "2026-10-05 01:01:00",
"company": "Example Corp"
}
]
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/transactions
List authenticated user card transactions
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[].id | string | yes | Card transaction ID |
data[].transaction_type | string | yes | Transaction type |
data[].amount | decimal-string | yes | Amount |
data[].currency | string | yes | Currency |
data[].mcc | string|null | yes | Merchant Category Codekey always present; value may be null |
data[].status | string | yes | Status |
data[].occurred_at | string | yes | Occurred at |
data[].card_id | string | yes | V-REC card ID |
data[].masked_pan | string|null | yes | Masked PANkey always present; value may be null |
{
"data": [
{
"id": "txn_demo",
"transaction_type": "PURCHASE",
"amount": "25.00",
"currency": "USD",
"mcc": "5812",
"status": "APPROVED",
"occurred_at": "2026-10-05 00:30:00",
"card_id": "card_demo",
"masked_pan": "**** **** **** 1234"
}
]
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards
List authenticated user cards
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array<card_list_item> | yes | User masked card list |
data[].id | string | yes | V-REC card public IDcards.public_id; card_... |
data[].card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
data[].brand | string|null | yes | Card brandcards.brand nullable |
data[].masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
data[].last4 | string|null | yes | Last four PAN digitsnullable |
data[].currency | string | yes | Card currencycards.currency |
data[].status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
data[].atm_enabled | integer | yes | Raw ATM flag from DB; card-list responses return JSON number 0 or 1raw cards.atm_enabled from PDO SELECT; JSON number 0 or 1 (ATTR_STRINGIFY_FETCHES=false); NOT boolean-cast |
data[].created_at | string | yes | Card record creation timecards.created_at NOT NULL DB datetime; key always present |
{
"data": [
{
"id": "card_demo",
"card_type": "VIRTUAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": false,
"created_at": "2026-10-01 00:00:00"
}
]
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/{type}
Issue a virtual card; physical issuance retired
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
type | string | yes | Issue typeVIRTUAL allowed; PHYSICAL returns 410 physical_card_issuance_retired (no issuer call) |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card public IDcards.public_id; card_... |
card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
brand | string|null | yes | Card brandcards.brand nullable |
masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
last4 | string|null | yes | Last four PAN digitsnullable |
currency | string | yes | Card currencycards.currency |
status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
atm_enabled | boolean | yes | ATM flag explicitly boolean-cast in mutation responsesexplicit (bool) cast by response cardShape(); key always present |
{
"id": "card_demo",
"card_type": "VIRTUAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": false
}
physical_card_issuance_retiredFormat: JSONPhysical issuance is retired; use physical/link for existing cards.
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf. Security::requireCsrf() runs before Auth::requireUser().
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerinvalid_card_type / kyc_not_approved / active_merchant_membership_required / sakura_cards_scope_required / vrec_instance_required / sakura_user_not_linkedFormat: JSONKYC/address/card issuance validation failed
card_issue_unavailableFormat: JSONCard provider temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/physical/link
Link a pre-issued physical card with end-user step-up
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | string | recommended | Provider idempotency keyoptional; trim; blank/omitted => normalized association number is used; non-empty >190 => idempotency_key_too_long |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
current_password | string | yes | Current V-REC passwordstep-up verification |
assoc_number | string | yes | Proxy / Association IDtrim -> remove ALL whitespace -> uppercase; then ^(?:PY)?\d{6,32}$; provider value normalized to PY + digits. Therefore raw form input containing spaces may still be accepted. |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card public IDcards.public_id; card_... |
card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
brand | string|null | yes | Card brandcards.brand nullable |
masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
last4 | string|null | yes | Last four PAN digitsnullable |
currency | string | yes | Card currencycards.currency |
status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
atm_enabled | boolean | yes | ATM flag explicitly boolean-cast in mutation responsesexplicit (bool) cast by response cardShape(); key always present |
{
"id": "card_demo",
"card_type": "PHYSICAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "ACTIVE",
"atm_enabled": true
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf. Security::requireCsrf() runs before Auth::requireUser().
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headercurrent_password_invalid / invalid_assoc_number / kyc_not_approved / physical_link_requires_sakura_driver / sakura_cards_scope_required / idempotency_key_too_long / active_merchant_membership_required / vrec_instance_required / sakura_user_not_linkedFormat: JSONStep-up or card-link validation failed
too_many_attemptsFormat: JSONMore than 8 attempts in 10 minutes
physical_card_link_unavailableFormat: JSONProvider temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/{id}/{action}
Change card status
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card IDcard_... |
action | string | yes | Card actioncase-insensitive: activate|unfreeze=>ACTIVE, freeze=>FROZEN, close=>CLOSED |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card public IDcards.public_id; card_... |
card_type | string | yes | Card typeVIRTUAL|PHYSICAL |
brand | string|null | yes | Card brandcards.brand nullable |
masked_pan | string|null | yes | Masked PANnever full PAN; nullable |
last4 | string|null | yes | Last four PAN digitsnullable |
currency | string | yes | Card currencycards.currency |
status | string | yes | Card statusACTIVE|FROZEN|CLOSED (stored/provider-normalized) |
atm_enabled | boolean | yes | ATM flag explicitly boolean-cast in mutation responsesexplicit (bool) cast by response cardShape(); key always present |
{
"id": "card_demo",
"card_type": "VIRTUAL",
"brand": "MASTERCARD",
"masked_pan": "**** **** **** 1234",
"last4": "1234",
"currency": "USD",
"status": "FROZEN",
"atm_enabled": false
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf. Security::requireCsrf() runs before Auth::requireUser().
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerunsupported_action / card_not_found / card_close_not_supported_by_connected_app / vrec_instance_required / sakura_user_not_linked / sakura_cards_scope_required / invalid_card_statusFormat: JSONUnsupported action or provider validation
card_status_unavailableFormat: JSONProvider temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/{id}/sensitive
Temporarily retrieve PAN/expiry/CVV after end-user step-up
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card IDcard_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
current_password | string | yes | Current V-REC passwordstep-up verification |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
card_number | string | yes | Full PAN; display transiently and do not persist |
expiry | string | yes | Expiry |
cvv | string | yes | CVV; never persist |
{
"card_number": "5555555555551234",
"expiry": "12/29",
"cvv": "123"
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf. Security::requireCsrf() runs before Auth::requireUser().
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headersakura_card_sensitive_scope_requiredFormat: JSONSAKURA Connected App sensitive-card scope missing
card_not_foundFormat: JSONCard not found for this user
current_password_invalid / card_sensitive_requires_sakura_driver / vrec_instance_required / sakura_user_not_linkedFormat: JSONStep-up or validation failure
too_many_attemptsFormat: JSONMore than 6 attempts in 5 minutes
card_sensitive_unavailableFormat: JSONProvider temporarily unavailable
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/{id}/pin
Set or change a 4-digit physical card PIN
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card IDcard_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionmust be a form/multipart field in $_POST; JSON body does not satisfy Security::requireCsrf() |
current_password | string | yes | Current V-REC passwordstep-up verification |
pin | string | yes | Four decimal digits for the new PIN^[0-9]{4}$; must match pin_confirmation |
pin_confirmation | string | yes | Repeat the new PINexactly equals pin |
No body or no endpoint-specific response schema.
{
"success": true
}
CSRF validation failedFormat: non-JSONMissing/invalid CSRF form field
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo active V-REC user session
empty body + Location headercard_not_foundFormat: JSONCard not found or not owned by the user
current_password_invalid / invalid_card_pin / physical_card_required / active_card_required / card_pin_requires_sakura_driver / sakura_card_sensitive_scope_required / matchmove_mobile_unavailableFormat: JSONPassword check failed, PIN invalid, card not physical/active, SAKURA connection missing or enrolled mobile unavailable
too_many_attemptsFormat: JSONMore than 4 attempts in 15 minutes
card_pin_unavailableFormat: JSONSAKURA or issuer PIN setting failed or could not be confirmed
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/cards/{id}/issuer-transactions
Read issuer transaction history for one owned card
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC card IDcard_... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
from | date | no | Start date (defaults to 30 days ago)YYYY-MM-DD, UTC |
to | date | no | End date (defaults to today)YYYY-MM-DD; from <= to; max range 92 days; no later than UTC tomorrow |
page | integer | no | Page number1-100; default 1 |
No body or no endpoint-specific response schema.
{
"items": [
{
"id": "txn_demo",
"type": "purchase",
"amount": "12.34",
"currency": "USD",
"description": "Demo merchant",
"merchant": "Demo merchant",
"status": "settled",
"date": "2026-10-17"
}
],
"page": 1,
"limit": 20,
"from": "2026-09-17",
"to": "2026-10-17",
"total": 1,
"has_more": false,
"source": "matchmove"
}
Location: /v/{instance}/loginFormat: redirectMissing V-REC session
empty body + Location headercard_not_foundFormat: JSONCard not found/not owned by user
invalid_page / invalid_date_range / sakura_cards_scope_requiredFormat: JSONInvalid page or date range, or missing SAKURA cards scope
too_many_attemptsFormat: JSONMore than 30 attempts/minute
card_transactions_unavailableFormat: JSONSAKURA/issuer unavailable or malformed response
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/tenant-allocations
List user tenant funding allocations
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array | yes | User-scoped tenant allocations (up to 100) |
data[].public_id | string | yes | Direct allocation reference |
data[].merchant_name | string | yes | Funding merchant label |
data[].amount | decimal string | yes | Amount in currency (not USDT) |
data[].currency | string | yes | USD |
data[].status | string | yes | COMPLETED or pending/failed state |
data[].remaining_amount | decimal string|null | yes | Remaining source lot; null before completion |
{
"data": [
{
"public_id": "vta_example",
"merchant_name": "Example Tenant",
"amount": "10.00000000",
"currency": "USD",
"status": "COMPLETED",
"remaining_amount": "10.00000000",
"created_at": "2026-10-17 02:29:51"
}
]
}
Location: /loginFormat: redirectAuthenticated V-REC user session required
Redirect to loginAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/tenant-returns
List user tenant funding returns
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array | yes | User-scoped tenant returns (up to 100) |
data[].public_id | string | yes | Return reference |
data[].amount | decimal string | yes | Amount in currency |
data[].currency | string | yes | USD |
data[].status | string | yes | COMPLETED or in-progress status |
{
"data": []
}
Location: /loginFormat: redirectAuthenticated V-REC user session required
Redirect to loginAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/ledger-entries
List attributed V-REC funding ledger entries
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data[] | array | yes | Attributed V-REC funding ledger, max 100 |
data[].public_id | string | yes | Ledger entry id |
data[].entry_type | string | yes | CREDIT, DEBIT, TENANT_RETURN, ... |
data[].amount | decimal string | yes | Signed source ledger amount |
data[].currency | string | yes | USD; never treated as token units |
data[].reference_type | string | yes | Source classification |
data[].reference_id | string | yes | Underlying event identifier |
{
"data": [
{
"public_id": "le_example",
"entry_type": "CREDIT",
"amount": "10.00000000",
"currency": "USD",
"reference_type": "SAKURA_TENANT_ALLOCATION",
"reference_id": "sakura_request_example"
}
]
}
Location: /loginFormat: redirectAuthenticated V-REC user session required
Redirect to loginAPI errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
https://v-rec.xyz
/api/v1/me/wallet
Wallet summary, USDT/ETH balances and gasless eligibility
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
skip_balances | any | no | When present, skip on-chain balance reads (faster) |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
enabled | boolean | yes | Whether the SAKURA-side V-REC wallet feature is enabled |
access.allowed | boolean | yes | Whether this user may create/transfer (SAKURA display mode / per-user enablement) |
access.reason | string|null | yes | Reason when not allowednot_enabled_for_user | disabled_by_operator | service_disabled |
features.wallet_creation / wallet_rotation / gasless_transfer | boolean | yes | SAKURA feature flags |
has_wallet | boolean | yes | Whether an active wallet exists |
wallet.address | string | conditional | Receiving address (Ethereum / ERC-20)^0x[0-9a-f]{40}$ |
wallet.version | integer | conditional | Increments on every rotation |
wallet.key_scheme | string | conditional | Key schemebip39_bip44 | legacy_random |
wallet.derivation_path | string|null | conditional | MetaMask-compatible derivation pathm/44'/60'/0'/0/0 |
balances.usdt.balance | decimal-string|null | conditional | On-chain USDT balance (6 dp); null if RPC failed |
balances.usdt.pending_incoming | decimal-string | conditional | Unconfirmed incoming USDT |
balances.usdt.available | decimal-string|null | conditional | Sendable = balance − pending_incoming (floored at 0) |
balances.eth.balance | decimal-string|null | conditional | ETH balance (18 dp); not required for gasless transfers |
balances.complete | boolean | conditional | Whether every balance was read |
gasless.allowed | boolean | yes | Whether gasless transfer is available now |
gasless.reason | string|null | yes | Eligibility reasonmerchant_managed | physical_card_exempt | not_subscriber | card_suspended | past_due | overdue | transfer_blocked ... |
gasless.message | string|null | yes | User-facing explanation (ja/en) |
gasless.daily_limit / daily_remaining | integer | yes | Daily attempt limit and remaining (failures count) |
transfer_blocked | boolean | yes | Transfers suspended by the operator |
previous_wallets[] | array<object> | yes | Rotated wallets (address, version, deactivated_at, deactivate_reason) |
{
"enabled": true,
"access": {
"allowed": true,
"reason": null
},
"features": {
"wallet_creation": true,
"wallet_rotation": true,
"gasless_transfer": true
},
"has_wallet": true,
"wallet": {
"address": "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be",
"version": 1,
"key_scheme": "bip39_bip44",
"derivation_path": "m/44'/60'/0'/0/0",
"seed_phrase_wallet": true,
"network": "ethereum",
"chain_id": 1,
"created_at": "2026-10-16T01:00:00+00:00"
},
"balances": {
"usdt": {
"symbol": "USDT",
"contract": "0xdac17f958d2ee523a2206206994597c13d831ec7",
"decimals": 6,
"balance": "120.500000",
"pending_incoming": "0.000000",
"available": "120.500000"
},
"eth": {
"symbol": "ETH",
"balance": "0.000000000000000000"
},
"complete": true,
"fetched_at": "2026-10-16T01:02:03+00:00"
},
"gasless": {
"allowed": true,
"reason": "merchant_managed",
"message": null,
"past_due_months": [],
"exempt_until": null,
"asset": "USDT",
"network": "ethereum",
"daily_limit": 5,
"daily_remaining": 4
},
"transfer_blocked": false,
"previous_wallets": []
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
wallet_service_unavailableFormat: JSONTemporary SAKURA / blockchain RPC failure; includes correlation_id
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/transfers
Transfer history and in-flight requests
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
limit | integer | no | Rows1..100, default 20 |
No body or no endpoint-specific response schema.
{
"data": [
{
"transfer_id": 41,
"status": "SUBMITTED",
"fee_mode": "gasless",
"symbol": "USDT",
"amount": "10.000000",
"from_address": "0x3f5c...",
"to_address": "0x9b2e...",
"tx_hash": "0xabc...",
"explorer_url": "https://etherscan.io/tx/0xabc...",
"created_at": "2026-10-16T01:10:00+00:00"
}
],
"requests": [
{
"public_id": "wrq_...",
"operation": "gasless_transfer",
"idempotency_key": "vwt_...",
"status": "SUCCEEDED",
"to_address": "0x9b2e...",
"amount": "10",
"tx_hash": "0xabc..."
}
]
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
wallet_service_unavailableFormat: JSONTemporary SAKURA / blockchain RPC failure; includes correlation_id
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/address-check
Pre-transfer destination safety check (address poisoning)
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field ($_POST); JSON body does not satisfy Security::requireCsrf() |
address | string | yes | Destination addressmax 64 |
No body or no endpoint-specific response schema.
{
"address": "0x9b2e...",
"verdict": "danger",
"risk_score": 85,
"checksum_valid": true,
"lookalike": "0x9b2e...1f",
"findings": [
{
"severity": "danger",
"message": "過去の送金先と先頭・末尾が一致する別アドレスです。"
}
],
"requires_acknowledgement": true
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
invalid_addressFormat: JSONMissing/too long
too_many_attemptsFormat: JSONMore than 60 per 10 minutes
wallet_service_unavailableFormat: JSONTemporary SAKURA / blockchain RPC failure; includes correlation_id
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet
Create wallet (returns the seed phrase once)
SAKURA crypto_wallet_manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | no | Prevents duplicate creation on retry^[A-Za-z0-9._:-]{1,120}$ |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field ($_POST); JSON body does not satisfy Security::requireCsrf() |
current_password | string | yes | V-REC sign-in password (step-up) |
wallet_password | string | yes | New wallet password (needed for every transfer)10..512 chars; should differ from the V-REC password |
wallet_password_confirmation | string | yes | Confirmationmust equal wallet_password |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
operation_id | string | yes | SAKURA operation idvwo_... |
operation | string | yes | Operationcreate | rotate |
replayed | boolean | yes | true when a stored result is returned for a retried idempotency key |
status | string | yes | ResultCREATED | ROTATED |
wallet | object | yes | New wallet (same shape as wallet_summary.wallet) |
previous_address | string | rotate only | Address before rotation |
mnemonic | string|null | yes | 12-word BIP-39 seed phrase, returned once only; null when replayed=trueNEVER log, store, cache or send to analytics |
mnemonic_available | boolean | yes | Whether mnemonic is present |
{
"operation_id": "vwo_...",
"idempotency_key": "vwc_...",
"operation": "create",
"replayed": false,
"status": "CREATED",
"wallet": {
"address": "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be",
"version": 1,
"key_scheme": "bip39_bip44",
"derivation_path": "m/44'/60'/0'/0/0",
"seed_phrase_wallet": true,
"network": "ethereum",
"chain_id": 1,
"created_at": "2026-10-16T01:00:00+00:00"
},
"mnemonic": "word1 word2 ... word12",
"mnemonic_words": 12,
"mnemonic_shown_once": true,
"mnemonic_available": true
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
wallet_access_denied / wallet_creation_disabledFormat: JSONUser or feature not enabled
wallet_already_existsFormat: JSONAn active wallet already exists (use rotate)
current_password_invalid / wallet_password_invalid_formatFormat: JSONStep-up failed / password rule
too_many_attemptsFormat: JSONMore than 8 step-ups per 10 minutes
wallet_creation_unavailable / wallet_service_unavailableFormat: JSONe.g. SAKURA recovery key not configured
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/rotate
Rotate wallet
SAKURA crypto_wallet_manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | no | Prevents a double rotation on retry^[A-Za-z0-9._:-]{1,120}$ |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field ($_POST); JSON body does not satisfy Security::requireCsrf() |
current_password | string | yes | V-REC sign-in password (step-up) |
new_wallet_password | string | yes | Password for the new wallet10..512 chars |
new_wallet_password_confirmation | string | yes | Confirmation |
reason | string | yes | Reasoncompromised | precaution | other |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
operation_id | string | yes | SAKURA operation idvwo_... |
operation | string | yes | Operationcreate | rotate |
replayed | boolean | yes | true when a stored result is returned for a retried idempotency key |
status | string | yes | ResultCREATED | ROTATED |
wallet | object | yes | New wallet (same shape as wallet_summary.wallet) |
previous_address | string | rotate only | Address before rotation |
mnemonic | string|null | yes | 12-word BIP-39 seed phrase, returned once only; null when replayed=trueNEVER log, store, cache or send to analytics |
mnemonic_available | boolean | yes | Whether mnemonic is present |
{
"operation_id": "vwo_...",
"operation": "rotate",
"replayed": false,
"status": "ROTATED",
"reason": "compromised",
"wallet": {
"address": "0x51e1...",
"version": 2,
"key_scheme": "bip39_bip44",
"derivation_path": "m/44'/60'/0'/0/0",
"seed_phrase_wallet": true,
"network": "ethereum",
"chain_id": 1,
"created_at": "2026-10-16T01:00:00+00:00"
},
"previous_address": "0x3f5c...",
"mnemonic": "word1 ... word12",
"mnemonic_available": true
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
wallet_not_foundFormat: JSONNothing to rotate (create first)
invalid_reason / current_password_invalid / wallet_password_invalid_formatFormat: JSONValidation
too_many_attemptsFormat: JSONStep-up limit
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/gasless-transfer
USDT gasless transfer (gas paid by SAKURA)
SAKURA crypto_wallet_manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | yes | Unique per transfer; reuse only to retry the same transfer^[A-Za-z0-9._:-]{1,120}$ |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field ($_POST); JSON body does not satisfy Security::requireCsrf() |
to_address | string | yes | Destination^0x[0-9a-fA-F]{40}$ |
amount | decimal-string | yes | USDT amount^\d{1,15}(\.\d{1,6})?$, > 0 |
wallet_password | string | yes | Wallet password (SAKURA decrypts the key to sign) |
risk_acknowledged | boolean-string | no | Acknowledge a dangerous destination1 | true |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
operation_id | string | yes | SAKURA operation idvwo_... |
status | string | yes | BroadcastSUBMITTED |
transfer_id | integer | yes | SAKURA transfer log id |
fee_mode | string | yes | Gas payergasless |
symbol / amount | string / decimal-string | yes | Asset and amount (6 dp)USDT |
from_address / to_address | string | yes | From / to^0x[0-9a-f]{40}$ |
tx_hash / explorer_url | string | yes | Transaction hash and Etherscan URL |
daily_remaining | integer | yes | Remaining attempts today |
request_key | string | yes | Key for status lookups (/api/v1/me/wallet/requests/{key}) |
{
"operation_id": "vwo_...",
"idempotency_key": "vwt_...",
"operation": "gasless_transfer",
"replayed": false,
"status": "SUBMITTED",
"transfer_id": 41,
"fee_mode": "gasless",
"symbol": "USDT",
"amount": "10.000000",
"from_address": "0x3f5c...",
"to_address": "0x9b2e...",
"tx_hash": "0xabc...",
"explorer_url": "https://etherscan.io/tx/0xabc...",
"daily_remaining": 3,
"request_key": "vwt_..."
}
CSRF validation failedFormat: non-JSONMissing/mismatched _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headerwallet_not_availableFormat: JSONSAKURA Connected App crypto_wallet (crypto_wallet_manage for writes) scope not granted
transfer_status_unknownFormat: JSONHTTP to SAKURA timed out; the transfer may still be processing. Do not resend — poll status_url
gasless_not_allowed / transfer_blocked / wallet_access_deniedFormat: JSONNot a subscriber, past due, suspended by operator, etc. (with reason)
wallet_not_foundFormat: JSONNo wallet
destination_risk / operation_in_progress / idempotency_key_reusedFormat: JSONDangerous destination (needs risk_acknowledged) / same key in progress / same key with different body
wallet_password_invalid / insufficient_balance / invalid_amount / invalid_destination / idempotency_key_requiredFormat: JSONInput or balance error
wallet_password_locked / daily_limit_reached / too_many_attemptsFormat: JSONPassword lockout (30 min) / daily limit / V-REC rate limit
wallet_operation_failedFormat: JSONOn-chain processing failed; with correlation_id. Check history, then retry with a NEW key
wallet_service_unavailableFormat: JSONTemporary SAKURA / blockchain RPC failure; includes correlation_id
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/requests/{key}
Look up a wallet request status
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
key | string | yes | request_key from the transfer or transfer_status_unknown response^[A-Za-z0-9._:-]{1,190}$ |
No body or no endpoint-specific response schema.
{
"operation_id": "vwo_...",
"idempotency_key": "vwt_...",
"operation": "gasless_transfer",
"status": "SUCCEEDED",
"error": null,
"created_at": "2026-10-16T01:10:00+00:00",
"updated_at": "2026-10-16T01:11:30+00:00",
"result": {
"status": "SUBMITTED",
"tx_hash": "0xabc..."
}
}
Location: /v/{instance}/loginFormat: redirectNo valid V-REC end-user session. This is not a JSON error body.
empty body + Location headeroperation_not_foundFormat: JSONNever reached SAKURA (safe to retry with a NEW key)
wallet_service_unavailableFormat: JSONTemporary SAKURA / blockchain RPC failure; includes correlation_id
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
https://v-rec.xyz
/api/v1/me/wallet/purchase-catalog
Products, payment assets, sellers and fees
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
No body or no endpoint-specific response schema.
{
"enabled": true,
"products": [
{
"code": "SGP_USD"
},
{
"code": "POINT",
"name": "ショップpt",
"points_per_usd": "100"
}
],
"payment_assets": [
{
"code": "USDT",
"seller": {
"SGP_USD": "SAKURA_EXCHANGE",
"POINT": "MERCHANT"
}
},
{
"code": "CUSTOM_TOKEN",
"symbol": "SHOP",
"unit_price_usd": "0.5",
"merchant_responsibility": true
}
],
"fees": {
"company_fee_rate": "0.01",
"merchant_royalty_rate": "0.005",
"total_rate": "0.015",
"standard_route_rate": "0.02"
}
}
Location: /v/{instance}/loginFormat: redirectNo end-user session
redirectwallet_not_availableFormat: JSONSAKURA crypto_wallet / crypto_wallet_manage scope not granted
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/purchase-quotes
Quote (fix the sale terms)
SAKURA crypto_wallet_manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
Idempotency-Key | header string | yes | Same contract on retry^[A-Za-z0-9._:-]{1,120}$ |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field |
product | string | yes | GoodsSGP_USD | POINT |
payment_asset | string | yes | Payment assetUSDT | CUSTOM_TOKEN |
payment_amount | decimal-string | yes | Payment amount> 0 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
contract_no | string | yes | Sale contract numberVCS-... |
contract_type | string | yes | Always SALE (not an exchange) |
status | string | yes | StatusQUOTED | PAYMENT_PROCESSING | PAYMENT_SUBMITTED | COMPLETED | FAILED | EXPIRED | MANUAL_REVIEW |
product / product_name / product_quantity | string | yes | Goods (SGP_USD or POINT) and quantity |
seller | string | yes | Seller: SAKURA_EXCHANGE only for SGP-USD paid in USDT; otherwise MERCHANTSAKURA_EXCHANGE | MERCHANT |
payment.asset / symbol / amount / unit_price_usd | string | yes | Price paid in crypto and unit price |
payment.pay_to_address | string | yes | Pay-to: company sales address for USDT; merchant address for custom token |
pricing.gross_usd | decimal-string | yes | USD value of the price (floor 6) |
pricing.company_fee_rate / company_fee_usd | decimal-string | yes | Company fee (1%; standard route 2%) |
pricing.merchant_royalty_rate / merchant_royalty_usd | decimal-string | yes | Merchant royalty (set by the merchant, added on top) |
pricing.net_usd | decimal-string | yes | Goods value = gross − fee − royalty |
merchant_responsibility | boolean | yes | Custom-token payment, accepted solely under the merchant's responsibility |
terms | object | detail only | Terms snapshot at quote time (ja/en) |
{
"contract_no": "VCS-20261016-AB12CD34EF",
"contract_type": "SALE",
"status": "QUOTED",
"product": "SGP_USD",
"product_name": "SGP-USD",
"product_quantity": "98.500000",
"seller": "SAKURA_EXCHANGE",
"payment": {
"asset": "USDT",
"symbol": "USDT",
"amount": "100.000000",
"unit_price_usd": "1.00000000",
"pay_to_address": "0x...",
"tx_hash": null
},
"pricing": {
"gross_usd": "100.000000",
"company_fee_rate": "0.01000000",
"company_fee_usd": "1.000000",
"merchant_royalty_rate": "0.00500000",
"merchant_royalty_usd": "0.500000",
"net_usd": "98.500000",
"points_per_usd": null
},
"merchant_responsibility": false,
"quote_expires_at": "2026-10-16T01:10:00+00:00",
"terms": {
"version": "2026-10-16",
"seller_name": "SAKURA EXCHANGE GUAM INC.",
"ja": [
"本契約は、購入者が暗号資産(USDT)を代金として、売主から「SGP-USD」を購入する売買契約です。通貨の両替ではありません。"
],
"en": [
"This is a sale contract …"
]
},
"replayed": false
}
CSRF validation failedFormat: non-JSONBad _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo end-user session
redirectwallet_not_availableFormat: JSONSAKURA crypto_wallet / crypto_wallet_manage scope not granted
product_not_offered / payment_asset_not_accepted / merchant_sales_not_enabledFormat: JSONNot offered
merchant_pool_insufficient / idempotency_key_reusedFormat: JSONMerchant pool insufficient etc.
invalid_amount / amount_out_of_range / amount_too_small / idempotency_key_requiredFormat: JSONInput error
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/purchases/{contract}/confirm
Accept terms and pay
SAKURA crypto_wallet_manage
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
contract | string | yes | Contract numberVCS-... |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
_csrf | string | yes | CSRF token bound to the V-REC sessionform field |
accept_terms | boolean-string | yes | Acceptance1 | true |
wallet_password | string | yes | Wallet password |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
contract_no | string | yes | Sale contract numberVCS-... |
contract_type | string | yes | Always SALE (not an exchange) |
status | string | yes | StatusQUOTED | PAYMENT_PROCESSING | PAYMENT_SUBMITTED | COMPLETED | FAILED | EXPIRED | MANUAL_REVIEW |
product / product_name / product_quantity | string | yes | Goods (SGP_USD or POINT) and quantity |
seller | string | yes | Seller: SAKURA_EXCHANGE only for SGP-USD paid in USDT; otherwise MERCHANTSAKURA_EXCHANGE | MERCHANT |
payment.asset / symbol / amount / unit_price_usd | string | yes | Price paid in crypto and unit price |
payment.pay_to_address | string | yes | Pay-to: company sales address for USDT; merchant address for custom token |
pricing.gross_usd | decimal-string | yes | USD value of the price (floor 6) |
pricing.company_fee_rate / company_fee_usd | decimal-string | yes | Company fee (1%; standard route 2%) |
pricing.merchant_royalty_rate / merchant_royalty_usd | decimal-string | yes | Merchant royalty (set by the merchant, added on top) |
pricing.net_usd | decimal-string | yes | Goods value = gross − fee − royalty |
merchant_responsibility | boolean | yes | Custom-token payment, accepted solely under the merchant's responsibility |
terms | object | detail only | Terms snapshot at quote time (ja/en) |
{
"contract_no": "VCS-...",
"status": "PAYMENT_SUBMITTED",
"payment": {
"tx_hash": "0xabc..."
},
"replayed": false
}
CSRF validation failedFormat: non-JSONBad _csrf
CSRF validation failedLocation: /v/{instance}/loginFormat: redirectNo end-user session
redirectpayment_status_unknownFormat: JSONHTTP timeout to SAKURA; poll status_url, do not resend
quote_expired / wallet_changed / merchant_collateral_shortfallFormat: JSONExpired etc.
terms_not_accepted / wallet_password_invalid / insufficient_balanceFormat: JSONNot accepted / wrong password (quote stays retryable)
wallet_password_locked / too_many_attemptsFormat: JSONLock etc.
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/purchases
List sale contracts
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
limit | integer | no | Rows1..100 |
No body or no endpoint-specific response schema.
{
"data": [
{
"contract_no": "VCS-...",
"status": "COMPLETED"
}
]
}
Location: /v/{instance}/loginFormat: redirectNo end-user session
redirectwallet_not_availableFormat: JSONSAKURA crypto_wallet / crypto_wallet_manage scope not granted
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/v1/me/wallet/purchases/{contract}
Get a sale contract
SAKURA crypto_wallet
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
V-REC session cookie | cookie | yes | Authenticated session cookie configured by SESSION_NAMEdefault configuration name: vrec_gateway; runtime-configurable |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
contract | string | yes | Contract numberVCS-... |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
contract_no | string | yes | Sale contract numberVCS-... |
contract_type | string | yes | Always SALE (not an exchange) |
status | string | yes | StatusQUOTED | PAYMENT_PROCESSING | PAYMENT_SUBMITTED | COMPLETED | FAILED | EXPIRED | MANUAL_REVIEW |
product / product_name / product_quantity | string | yes | Goods (SGP_USD or POINT) and quantity |
seller | string | yes | Seller: SAKURA_EXCHANGE only for SGP-USD paid in USDT; otherwise MERCHANTSAKURA_EXCHANGE | MERCHANT |
payment.asset / symbol / amount / unit_price_usd | string | yes | Price paid in crypto and unit price |
payment.pay_to_address | string | yes | Pay-to: company sales address for USDT; merchant address for custom token |
pricing.gross_usd | decimal-string | yes | USD value of the price (floor 6) |
pricing.company_fee_rate / company_fee_usd | decimal-string | yes | Company fee (1%; standard route 2%) |
pricing.merchant_royalty_rate / merchant_royalty_usd | decimal-string | yes | Merchant royalty (set by the merchant, added on top) |
pricing.net_usd | decimal-string | yes | Goods value = gross − fee − royalty |
merchant_responsibility | boolean | yes | Custom-token payment, accepted solely under the merchant's responsibility |
terms | object | detail only | Terms snapshot at quote time (ja/en) |
{
"contract_no": "VCS-20261016-AB12CD34EF",
"contract_type": "SALE",
"status": "COMPLETED",
"product": "SGP_USD",
"product_name": "SGP-USD",
"product_quantity": "98.500000",
"seller": "SAKURA_EXCHANGE",
"payment": {
"asset": "USDT",
"symbol": "USDT",
"amount": "100.000000",
"unit_price_usd": "1.00000000",
"pay_to_address": "0x...",
"tx_hash": null
},
"pricing": {
"gross_usd": "100.000000",
"company_fee_rate": "0.01000000",
"company_fee_usd": "1.000000",
"merchant_royalty_rate": "0.00500000",
"merchant_royalty_usd": "0.500000",
"net_usd": "98.500000",
"points_per_usd": null
},
"merchant_responsibility": false,
"quote_expires_at": "2026-10-16T01:10:00+00:00",
"terms": {
"version": "2026-10-16",
"seller_name": "SAKURA EXCHANGE GUAM INC.",
"ja": [
"本契約は、購入者が暗号資産(USDT)を代金として、売主から「SGP-USD」を購入する売買契約です。通貨の両替ではありません。"
],
"en": [
"This is a sale contract …"
]
},
"replayed": false
}
Location: /v/{instance}/loginFormat: redirectNo end-user session
redirectsale_not_foundFormat: JSONNot found
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1
/api/merchant/v1/learning/programs
List up to 100 published courses belonging to this merchant
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data | array | yes | 公開済み講座(最大100件) / Merchant-published programs公開済み講座(最大100件) / Merchant-published programs |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/programs/{id}
Merchant course details and enrollment conditions
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | integer | yes | Program ID>0 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data | array | yes | 公開済み講座(最大100件) / Merchant-published programs公開済み講座(最大100件) / Merchant-published programs |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/programs/{id}/enrollments
Consent-based free-course enrollment
learning.enroll.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | integer | yes | Program ID>0 |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC user public IDusr_... |
learner_consent | boolean | yes | Member consenttrue |
consent_reference | string | yes | Merchant consent reference8..100 characters |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data | array | yes | 公開済み講座(最大100件) / Merchant-published programs公開済み講座(最大100件) / Merchant-published programs |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/programs/{id}/sessions
Get lesson schedule and completion rules
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | integer | yes | SAKURA program ID (positive integer)SAKURA program ID (positive integer) |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
program | object | yes | 対象講座 / Course対象講座 / Course |
sessions | array | yes | キャンセル除外の授業 / Non-cancelled lessonsキャンセル除外の授業 / Non-cancelled lessons |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/users/{id}/schedule
Read member course schedule
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC usr_... ID (ACTIVE/FULL)V-REC usr_... ID (ACTIVE/FULL) |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC public user ID (usr_...)V-REC usr_... |
data | array | yes | 加入済み講座・授業 / Enrolled courses and sessions加入済み講座・授業 / Enrolled courses and sessions |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/users/{id}/progress
Read member attendance progress and Google Calendar add URLs
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC usr_... ID (ACTIVE/FULL)V-REC usr_... ID (ACTIVE/FULL) |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC usr_... member IDV-REC usr_... member ID |
google_calendar_connected | boolean | yes | Google連携設定 / Google authorization statusGoogle連携設定 / Google authorization status |
data | array | yes | 授業毎のcompleted_at, completion_status, duration_seconds, calendar_sync_status, google_calendar_add_url授業毎のcompleted_at, completion_status, duration_seconds, calendar_sync_status, google_calendar_add_url |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/rewards
Read recent custom token learning rewards
learning.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
session_id | integer | no | optional positive IDoptional positive ID |
program_id | integer | no | optional positive IDoptional positive ID |
limit | integer | no | 1–100; default 501–100; default 50 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data | array | yes | Up to 100 rows. Each user_id is V-REC usr_...; internal SAKURA user_ref is omitted.カスタムトークン付与履歴(最大100件) / Last up to 100 reward rows |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/sessions/{id}/complete
Submit external lesson completion evidence
learning.rewards.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | integer | yes | SAKURA session IDSAKURA session ID |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC usr_... IDV-REC usr_... ID |
idempotency_key | string | yes | 8–120 chars [A-Za-z0-9._:-]8–120 chars [A-Za-z0-9._:-] |
evidence_ref | string | yes | 8–190 chars; auditable provider evidence8–190 chars; auditable provider evidence |
duration_seconds | integer | yes | 1–864001–86400 |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
session_id | integer | yes | 授業ID / Lesson ID授業ID / Lesson ID |
attendance_status | string | yes | 再判定後の出席ステータス / Assessed attendance再判定後の出席ステータス / Assessed attendance |
reward | object|null | conditional | 付与台帳の状態。nullの場合もある / May be null付与台帳の状態。nullの場合もある / May be null |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
validation_errorFormat: JSONInvalid fields, eligibility, balance or wallet
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/sessions/{id}/rewards
Retry configured reward for completed lesson
learning.rewards.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | integer | yes | SAKURA session IDSAKURA session ID |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC usr_... IDV-REC usr_... ID |
idempotency_key | string | yes | 8–120 chars [A-Za-z0-9._:-]8–120 chars [A-Za-z0-9._:-] |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
reward | object | yes | 既存設定のトークン付与結果 / Existing configured grant record既存設定のトークン付与結果 / Existing configured grant record |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
validation_errorFormat: JSONInvalid fields, eligibility, balance or wallet
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/learning/users/{id}/calendar-sync
Sync enrolled lessons to approved Google Calendar
learning.calendar.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | V-REC usr_... IDV-REC usr_... ID |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
program_id | integer | yes | positive integer, enrolled programpositive integer, enrolled program |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
user_id | string | yes | V-REC member IDV-REC member ID |
sync | object | yes | 同期件数・失敗数 / Sync and failure counts同期件数・失敗数 / Sync and failure counts |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
validation_errorFormat: JSONInvalid fields, eligibility, balance or wallet
partial_calendar_syncFormat: JSONPartial calendar sync failure
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/token-pool
Read dedicated ERC-20 pool and all-time ledger statistics
learning.token_pool.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
contract | string | yes | ERC-20 contractERC-20 contract |
symbol | string | yes | Token symbolToken symbol |
onchain_balance | string|null | conditional | オンチェーン残高取得結果 / Chain balance when availableオンチェーン残高取得結果 / Chain balance when available |
reserved_amount | string | yes | 予約済み数量 / Reserved amount予約済み数量 / Reserved amount |
available_estimate | string|null | conditional | 参考利用可能量(確定残高ではない) / Estimated available参考利用可能量(確定残高ではない) / Estimated available |
statistics | object | yes | 全期間集計 / All-time totals全期間集計 / All-time totals |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/token-pool/transfers
List pool transfers, statistics and cursor pagination
learning.token_pool.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
limit | integer | no | 1–100, default 501–100, default 50 |
before_id | integer | no | positive cursor IDpositive cursor ID |
status | string | no | reserved|dispatching|submitted|confirmed|reconcile_required|failedreserved|dispatching|submitted|confirmed|reconcile_required|failed |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
data | array | yes | 送金台帳の一ページ / One ledger page送金台帳の一ページ / One ledger page |
statistics | object | yes | 全期間集計(total_count, requested_amount, submitted_count, confirmed_count, reconcile_required_count 等)全期間集計(total_count, requested_amount, submitted_count, confirmed_count, reconcile_required_count 等) |
pagination | object | yes | limit, has_more, next_before_idlimit, has_more, next_before_id |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/token-pool/transfers/{id}
Read a submitted transfer by request ID
learning.token_pool.read
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
id | string | yes | request_id 8–120 [A-Za-z0-9._:-]request_id 8–120 [A-Za-z0-9._:-] |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
request_id | string | yes | 加盟店送金リクエストID / Merchant idempotency identifier加盟店送金リクエストID / Merchant idempotency identifier |
status | string | yes | reserved|dispatching|submitted|confirmed|reconcile_required|failedreserved|dispatching|submitted|confirmed|reconcile_required|failed |
tx_hash | string|null | conditional | 送信受付時に存在することがある / Optional hash送信受付時に存在することがある / Optional hash |
amount | string | yes | 数量 / Exact decimal string数量 / Exact decimal string |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
/api/merchant/v1/token-pool/transfers
Request gasless ERC-20 transfer from dedicated merchant pool
learning.token_pool.write
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
X-VREC-Merchant-Key | header string | yes | Merchant API credential key IDactive credential key |
X-VREC-Timestamp | header integer-string | yes | Unix seconds; must be within ±300 seconds of server time(int) cast; outside ±300 => merchant_timestamp_invalid |
X-VREC-Nonce | header string | yes | Replay-protection nonce; cannot be reused for the same credential^[A-Za-z0-9._:-]{8,128}$; stored for 10 minutes |
X-VREC-Signature | header string | yes | Lowercase hex HMAC-SHA256 of the canonical stringhash_hmac(sha256); compared after lowercasing header |
| Field | Type | Required | Constraint / meaning |
|---|---|---|---|
merchant_code | string | yes | V-REC mrc_... matches HMAC merchantV-REC mrc_... matches HMAC merchant |
user_id | string | yes | V-REC usr_... ACTIVE/FULL memberV-REC usr_... ACTIVE/FULL member |
request_id | string | yes | unique 8–120 [A-Za-z0-9._:-]unique 8–120 [A-Za-z0-9._:-] |
amount | string | yes | decimal string >0, <=18 decimal digits before token precision validationdecimal string >0, <=18 decimal digits before token precision validation |
| Field | Type | Present | Constraint / meaning |
|---|---|---|---|
request_id | string | yes | 加盟店送金リクエストID / Merchant idempotency identifier加盟店送金リクエストID / Merchant idempotency identifier |
status | string | yes | reserved|dispatching|submitted|confirmed|reconcile_required|failedreserved|dispatching|submitted|confirmed|reconcile_required|failed |
tx_hash | string|null | conditional | 送信受付時に存在することがある / Optional hash送信受付時に存在することがある / Optional hash |
amount | string | yes | 数量 / Exact decimal string数量 / Exact decimal string |
merchant_auth_requiredFormat: JSONHMAC認証に失敗 / HMAC authentication failed
merchant_scope_deniedFormat: JSONHMAC scope、所属、SAKURA Connected Appsが不一致 / insufficient grants or merchant mismatch
merchant_learning_shared_instance_not_isolatedFormat: JSON共有インスタンスは加盟店分離未保証 / shared instance is not isolated
learning_upstream_unavailableFormat: JSONSAKURA通信障害。送金の自動再試行は禁止 / Upstream unavailable; do not blindly retry money movement
validation_errorFormat: JSONInvalid fields, eligibility, balance or wallet
API errors are JSON by default {"error":"<machine_code>"} except responses explicitly marked non-JSON / redirect (including the current end-user session 302 and CSRF 419 behavior).
Webhook lifecycle reference: timing, states, registration, tenant funding and 3DS →
V-REC → MerchantMerchant configured HTTPS URLuser.kyc.updated⌄Content-Type: application/json
X-VREC-Event: user.kyc.updated
X-VREC-Delivery: mwd_...
X-VREC-Timestamp: <unix seconds>
X-VREC-Signature: <lowercase hex hmac sha256>TIMESTAMP + "\n" + DELIVERY + "\n" + EVENT + "\n" + SHA256(raw_body)Any 2xx is success. Failed deliveries make at most 8 attempts with delays of 60, 300, 1800, 7200, 21600, 43200 and 86400 seconds. De-duplicate transport by X-VREC-Delivery and business events by payload id.
Expected: HTTP 200–299
| Payload field | Type | Required | Meaning |
|---|---|---|---|
id | string | yes | Stable business event ID across retrieskyc_ + sha256(membership_id|status|source_event_id); stable across retries |
type | string | yes | Event typeuser.kyc.updated |
occurred_at | string | yes | Occurred atISO 8601 |
data.user_id | string | yes | V-REC user ID |
data.beneficiary_id | string | yes | Beneficiary ID |
data.external_user_ref | string|null | yes | Merchant external user referenceJSON key is always present; value may be null |
data.kyc_status | string | yes | Latest KYC status re-read from SAKURA |
data.ekyc_status | string|null | yes | Informational eKYC stateJSON key is always present; value may be null |
{
"id": "kyc_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"type": "user.kyc.updated",
"occurred_at": "2026-10-05T01:00:00Z",
"data": {
"user_id": "usr_demo",
"beneficiary_id": "VREC-DEMO",
"external_user_ref": null,
"kyc_status": "APPROVED",
"ekyc_status": "COMPLETED"
}
}Full PAN / expiry / CVV are returned only transiently through the authenticated end-user sensitive endpoint after current-password step-up and CSRF, with no-store handling. eKYC redirect URLs are likewise restricted to the authenticated user session.
Internal APIs (/api/internal/v1/*) and the SAKURA Connected App bridge are outside this public contract.