Reference · Business Wallet API · v1
Every endpoint below is served by business-wallet.ashx on the tenant hostname. All endpoints are HTTPS-only. Authentication is one of: none (public), OIDC bearer (admin), HMAC token (public but proof-signed), or invite HMAC (public, invite-signed).
companyId ([A-Za-z0-9_-]{4,40}) selected via ?bw=<companyId>. Path-scoped DID: did:web:<tenant>:business-wallets:<companyId>. Storage: App_Data/<tenant>/business-wallets/<companyId>/.| Method | Path | Auth | Description |
|---|---|---|---|
| GET | ?action=list-wallets | public | List PUBLIC wallets on this tenant (filters phantom dirs by requiring state.json; excludes wallets marked publish=false). |
| GET | ?action=list-my-wallets | bearer | List wallets on this tenant where the caller (email or wallet_id) appears in the employees table. Includes private wallets the caller belongs to. |
| GET | ?action=list-all-wallets | bearer | Tenant-admin enumeration. Returns every wallet regardless of the publish flag, including _admin sink and .deleted-* tombs categorised in the response for the admin dashboard. |
| POST | ?action=create-wallet[&slug=<hint>] | bearer | Mint a new wallet with fresh companyId. Body: {name*, reg, juris, vat, addr}. Creator auto-enrolled as director. |
| POST | ?action=delete-wallet&bw=<cid> | bearer | Tenant-admin only. Moves the wallet directory to .deleted-<cid>-<ts> tomb (never hard-deleted). Bearer requires tenant-admin scope; the wallet's own directors do not qualify. |
| POST | ?action=delete-phantoms | bearer | Sweeps wallet directories missing state.json (orphans) into the trash and returns a categorised report {orphans, tombs, admin_sinks, kept}. Idempotent. |
| GET | ?action=identity[&bw=<cid>] | public | Tenant-level identity if bw omitted; per-wallet identity when supplied. |
| GET | ?action=public-view&bw=<cid> | public | Anonymous read-only projection of a wallet. Returns {company{name,reg,juris,vat,addr,did}, employees[{name,role}], doc_count, vc_count, tenant, wallet_id, bw}. No email, no wallet_id per employee, no doc contents, no audit, no RPs. Private wallets return 404 wallet_not_found to unauthenticated callers (indistinguishable from a wallet that does not exist). |
| GET | /business-wallets/<cid>/did.json | public | DID Document for this business (path-rewritten to ?action=bw-did-doc). 404 if wallet doesn't exist. |
| POST | ?action=resolve-identity | public | Best-effort lookup of {name, email} from a DID (fetches remote OpenID Federation entity statement). SSRF-hardened, HTTPS-only, no redirects. |
{
"tenant": "phone.aloaha.com",
"count": 2,
"wallets": [
{ "id": "acme-ltd",
"name": "Acme Ltd",
"reg": "C-99999",
"juris": "MT",
"vat": "MT12345678",
"did": "did:web:phone.aloaha.com:business-wallets:acme-ltd",
"did_doc_url": "https://phone.aloaha.com/business-wallets/acme-ltd/did.json",
"url": "https://phone.aloaha.com/web-business-wallet.html?bw=acme-ltd",
"wallet_id": "urn:aloaha:web-wallet:xyz9YtGL8W...",
"created": "2026-08-07T09:12:44Z" }
]
}
POST /business-wallet.ashx?action=create-wallet&slug=acme-ltd
Authorization: Bearer <oidc-token>
Content-Type: application/json
{ "name": "Acme Ltd",
"reg": "C-99999",
"juris":"MT",
"vat": "MT12345678",
"addr": "Level 3, Sky Parks, Malta" }
| Method | Action (all require &bw=<cid>) | Auth | Description |
|---|---|---|---|
| GET | get-state | bearer | Company profile + employees + relying parties + doc index + audit-log count. |
| POST | save-company | bearer | Update company block. Body: {name, reg, juris, vat, addr, didMethod}. |
| POST | add-employee | bearer | Add employee. Body: {name*, email?, wallet_id?, role}. At least one of email or wallet_id required. |
| POST | update-employee-role | bearer | {id, role} where role is director|secretary|legal|compliance|employee. |
| POST | remove-employee | bearer | {id}. |
| POST | add-rp | bearer | Register relying party. Body: {name, kind, did, email?, mobile?}. DID accepts did:web:, did:key:, did:jwk:, did:webvh:, or urn:aloaha:web-wallet:. |
| POST | remove-rp | bearer | {id}. Cascade-revokes all doc grants for that RP. |
| Method | Action (+&bw=<cid>) | Auth | Description |
|---|---|---|---|
| GET | list-docs | bearer | All docs' meta.json for this wallet. |
| POST | upload-doc | bearer | Multipart. Fields: file, title, type, issuer, sign_by_uploader, wallet_seal. When either checkbox is set and the file is a PDF, PadesInlineSigner.SignPdfBB seals the PDF synchronously before storage. |
| GET | download-doc&id=<docId> | bearer | Streams the stored (signed) bytes. |
| POST | delete-doc | bearer | {id}. Removes .bin + .meta.json. |
| POST | grant-doc | bearer | {docId, rpId}. Grants an RP read access. |
| POST | revoke-doc | bearer | {docId, rpId}. |
| POST | share-now | bearer | Simulate fan-out reads to every granted RP. Writes audit entries. |
| GET | list-shared-in | bearer | Documents that OTHER wallets on this tenant have granted to this wallet (via an RP enrollment that names our did:web or urn:aloaha:web-wallet:). Read-only, capped at 500 shared docs. Skips _admin sink and .deleted-* tombs. Returns {shared:[{doc_id, doc_title, doc_type, doc_vct, doc_issuer, doc_size, shared_by_wallet_id, shared_by_wallet_name, shared_by_did, granted_via_rp_name, download_url, shared_at}], count, scanned_wallets}. |
| GET | cross-download&from=<theirCid>&doc=<docId> | bearer | Stream bytes of a doc that theirCid has granted TO the caller's wallet. Server re-verifies the grant (RP enrolled by theirCid must carry our DID or URN, and meta.grants must include that RP). 403 not_granted_to_you if the check fails. Audit lines land in BOTH wallets. |
| Method | Action | Auth | Description |
|---|---|---|---|
| POST | create-share-link (+&bw) | bearer | Body: {docId, ttl_sec}. Returns {url, status_index, revoke_url}. Token layout: <cid>.<docId>.<exp>.<sig>.<statusIdx>. Canon: bw|share|<tenant>|<cid>|<docId>|<exp>|<statusIdx>. |
| GET | share-download&t=<token> | HMAC | Public. Bounces 404 if wallet doesn't exist (no side-effect dir creation). |
| POST | revoke-share&index=<N> | bearer | Sets bit N in the wallet's Bitstring Status List. Existing tokens fail with 410. |
| POST | invite-countersign (+&bw) | bearer | Body: {docId, inviteeName, inviteeEmail, inviteeMobile, inviteeDid, status, ttl_sec}. Returns HMAC-signed invite URL. |
| POST | countersign | invite OR HMAC/DID/PID/OIDC | Two modes: invite_token (public with valid invite) OR direct auth via IdentifyRegistryPusher. |
| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| POST | wia-for-employee | bearer | Mints a WIA JWT (typ=wallet-attestation+jwt, ES256) over a caller-supplied public JWK. Client keeps the private half; server never sees it. Body: {empId, cnf_jwk:{kty:"EC",crv:"P-256",x,y}}. |
| POST | issue-employee-cred | bearer | OID4VCI pre-authorised push from company to employee. Writes offer under App_Data/<tenant>/vci/offers/<offerId>.json (contract matches vci.ashx exactly). Body: {empId, vct, claims}. Returns {offer_uri, deep_link, pre_authorized_code, expires_utc}. |
| POST | mdoc-issue | bearer | Enqueues an mDoc (COSE_Sign1) signing job. Body: {empId, docType}. |
| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| POST | store-vc | bearer | Body: {vc_jwt}. Stores at vcs/<vcId>.jwt + meta. vcId = sha256(vc_jwt)[:16]. |
| GET | list-vc | bearer | Array of {vcId, vct, issuer, iss, exp, iat, sub}. |
| GET | get-vc&id=<vcId> | bearer | Raw JWT for client-side verification + card render. |
| POST | delete-vc | bearer | {id}. |
| POST | verify-vc | bearer | Loopback to /vcdm.ashx?action=verify; falls back to in-process ES256 verify. Returns {ok, verified, reason}. |
Banks, business registries, notaries push documents into a wallet without needing an OIDC account. Three auth methods:
X-Push-Auth: company + X-Push-Did: did:web:<X> where X is in this tenant's trust list. Actor logged as registry:<did>.X-Push-Auth: pid + X-Push-Vp: <SD-JWT VC>. VP verified via /vcdm.ashx?action=verify. Actor logged as pid:<subject>.oidc:<email>.| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| POST | registry-push | one of 3 | Multipart upload same shape as upload-doc. Notifies directors + secretaries by email/WhatsApp. |
| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| POST | wallet-export | bearer | Body: {passphrase}. Returns encrypted binary blob (CBBW2 header, AES-256-CBC + HMAC-SHA256 encrypt-then-MAC covering hdr+salt+IV+ct, PBKDF2 100k iterations). |
| POST | wallet-import | bearer | Verify-only in current release. Decrypts + MAC-verifies; returns {verified:true, restored:false}. |
| GET | ble-presentation&mode=qr|ble|nfc | bearer | Returns a 5-minute presentation nonce + deep link (mdoc-openid4vp://<host>?nonce=<N>). |
| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| GET | audit-log&limit=<N> | bearer | Tail N entries (default 100, max 2000). Each entry: {ts, actor, action, target}. |
Any employee whose role is agent becomes a machine identity in the wallet. Each agent gets a fragment DID under the wallet, has a stored capability list and a maximum token TTL, and can be issued short-lived ES256 capability tokens signed by the tenant issuer. Downstream services fetch the public agent manifest to learn what the agent is + who owns it, then call verify-agent-token on any received token. See the explainer at ai-agent-identity.html.
POST /business-wallet.ashx?action=add-employee&bw=<cid>
Authorization: Bearer <OIDC token>
Content-Type: application/json
{
"name": "invoice-triage",
"role": "agent",
"agent_model": "large-model",
"agent_provider_hint": "acme-agents",
"agent_purpose": "Reads incoming invoices and files them",
"agent_capabilities": ["list-docs","download-doc","verify-vc","file-invoice"],
"agent_max_ttl_sec": 3600
}
Non-agent roles that carry any agent_* field are rejected with 400 agent_fields_only_valid_for_agent_role. Capability entries limited to 64 chars, [a-zA-Z0-9._-]. TTL clamped to 60–86400 s.
| Method | Action (+&bw) | Auth | Description |
|---|---|---|---|
| GET | list-agents | bearer | List every employee row with role=agent. Returns {agents, count}. |
| POST | mint-agent-token | bearer | Body {empId, ttl_sec, scope[], audience?, purpose_hint?}. Mints ES256 JWT (typ=codeb-agent-token+jwt) signed by tenant issuer. |
| POST | deactivate-agent | bearer | Body {id}. Flips agent_active=false. Existing tokens fail verification immediately. |
| GET | agent-manifest&emp=<empId> | public | Public descriptor: name, model, capabilities, active flag, DID, owner DID, issuer JWK, revocation URL. |
| GET/POST | verify-agent-token | public | Body {token} (POST) or ?token= (GET). Returns {verified, agent_did, owner_did, scope, exp, reason?}. |
| GET | agent-status-list | public | W3C BitstringStatusList v1.0 stub. Per-token bit-flipped revocation to follow. |
{
"ok": true,
"token": "eyJhbGciOiJFUzI1NiIsInR5cCI6ImNvZGViLWFnZW50LXRva2VuK2p3dCJ9....",
"exp": 1786320000,
"iat": 1786316400,
"agent_did": "did:web:tenant.example:business-wallets:acme-ltd#agent-e_abc123",
"owner_did": "did:web:tenant.example:business-wallets:acme-ltd",
"scope": ["list-docs","download-doc","verify-vc"],
"typ": "codeb-agent-token+jwt",
"ttl_sec": 3600
}
{
"id": "e_abc123",
"name": "invoice-triage",
"role": "agent",
"agent_model": "large-model",
"agent_provider_hint": "acme-agents",
"agent_purpose": "Reads incoming invoices and files them",
"agent_capabilities": ["list-docs","download-doc","verify-vc"],
"agent_max_ttl_sec": 3600,
"agent_active": true,
"agent_did": "did:web:tenant.example:business-wallets:acme-ltd#agent-e_abc123",
"owner_did": "did:web:tenant.example:business-wallets:acme-ltd",
"issuer": "did:web:tenant.example:business-wallets:acme-ltd",
"manifest_url": "https://tenant.example/business-wallet.ashx?action=agent-manifest&bw=acme-ltd&emp=e_abc123",
"did_doc_url": "https://tenant.example/business-wallets/acme-ltd/did.json",
"token_type": "codeb-agent-token+jwt",
"issuer_key": { "kty":"EC","crv":"P-256","x":"...","y":"...","use":"sig","alg":"ES256" },
"revocation": {
"type": "BitstringStatusList",
"url": "https://tenant.example/business-wallet.ashx?action=agent-status-list&bw=acme-ltd"
}
}
{
"verified": true,
"reason": "ok",
"agent_did": "did:web:...#agent-e_abc123",
"owner_did": "did:web:...",
"iss": "did:web:...",
"exp": 1786320000,
"scope": ["list-docs","download-doc"],
"typ": "codeb-agent-token+jwt"
}
Failure reasons: not_compact_jws, malformed_segment:*, unsupported_alg:*, token_expired, iss_not_this_tenant, iss_bad_companyId, agent_did_missing_fragment, issuer_pfx_missing, bad_signature, agent_not_found, employee_not_agent, agent_deactivated. Verifier never leaks internal state on failure — every reason is a stable machine-readable slug.
sipprov.ashx)Short-TTL HMAC token that resolves to a Zoiper XML, Linphone .rc, or JSON provisioning payload. Prevents plaintext SIP passwords in QR codes.
| POST | ?action=create-token | bearer | Body: {aor, password, ttl, format, label}. Returns short URL + expiry. |
| GET | ?t=<token>[&format=zoiper|linphone|json] | HMAC | Single-use. Delete-before-write race guard. |
| POST | ?action=revoke&tid=<tid> | bearer | Force-expire a token. |
| GET | ?action=list | bearer | Outstanding tokens for this tenant. |
OID4VP 1.0 FINAL · OID4VCI Draft-15 · SD-JWT VC draft-07 · W3C VCDM 2.0 (JOSE envelope) · W3C DID Core 1.0 · W3C Bitstring Status List v1.0 · ETSI TS 119 602 Trusted List · ETSI EN 319 102-1 PAdES-B-B · HAIP §5.11 Wallet Attestation · European Digital Identity Wallet (eIDAS 2.0, ARF 3.0).
The CodeB Business Wallet API is the REST interface of the CodeB Business Wallet, served by business-wallet.ashx on the tenant hostname over HTTPS only. It covers wallet lifecycle, company profile and employees, signed document storage and sharing, credential issuance to employees, a company VC vault, encrypted backup, audit logs and AI agent identities. One tenant can host many business wallets.
Each CodeB Business Wallet API endpoint uses one of four authentication modes: none (public), an OIDC bearer token (admin), an HMAC token (public but proof-signed), or an invite HMAC (public, invite-signed). Most management actions require the OIDC bearer, while share-download links and countersign invites are HMAC-signed.
Each wallet in the CodeB Business Wallet API has a companyId matching [A-Za-z0-9_-]{4,40}, selected with the ?bw=<companyId> query parameter. The wallet gets a path-scoped DID of the form did:web:<tenant>:business-wallets:<companyId>, and its DID document is served publicly at /business-wallets/<companyId>/did.json. A new wallet is created with POST ?action=create-wallet, and the creator is auto-enrolled as director.
The CodeB Business Wallet API signs PDFs at upload time: when upload-doc is called with sign_by_uploader or wallet_seal set and the file is a PDF, the server seals it as PAdES-B-B synchronously before storage. download-doc then streams the stored signed bytes. Documents can be granted to or revoked from registered relying parties per document.
The CodeB Business Wallet API creates external share links with create-share-link, which returns an HMAC-signed URL with a caller-chosen TTL and a status-list index. Calling revoke-share sets that bit in the wallet's Bitstring Status List, after which existing tokens fail with HTTP 410. For signatures from outside parties, invite-countersign returns an HMAC-signed invite URL.
Yes, the CodeB Business Wallet API issues credentials to employees. issue-employee-cred creates an OID4VCI pre-authorised offer and returns an offer_uri, deep link and pre-authorized code, mdoc-issue enqueues an mDoc (COSE_Sign1) signing job, and wia-for-employee mints a Wallet-Instance-Attestation JWT over a public key supplied by the client, so the server never sees the private key.
Yes, the CodeB Business Wallet API has a registry-push action for external submitters such as banks, business registries and notaries. It accepts three authentication methods: a company DID that is in the tenant's trust list, a personal PID presented as an SD-JWT VC, or a standard OIDC bearer token. Directors and secretaries are notified by email or WhatsApp when a document arrives.
Yes, any employee added with role agent becomes a machine identity with a fragment DID under the wallet, a stored capability list and a maximum token TTL. mint-agent-token issues short-lived ES256 capability tokens signed by the tenant issuer, downstream services check them with the public verify-agent-token endpoint, and deactivate-agent makes existing tokens fail verification immediately.
No, delete-wallet in the CodeB Business Wallet API is not a hard delete. The action is restricted to tenant admins and moves the wallet directory to a .deleted-<companyId>-<timestamp> tomb, and the wallet's own directors cannot delete it.
The CodeB Business Wallet API exports a wallet with wallet-export, which returns a passphrase-encrypted blob using AES-256-CBC with HMAC-SHA256 encrypt-then-MAC and PBKDF2 at 100k iterations. In the current release wallet-import is verify-only: it decrypts and MAC-verifies the blob and returns verified true, restored false.
The CodeB Business Wallet API lists OID4VP 1.0 Final, OID4VCI Draft-15, SD-JWT VC draft-07, W3C VCDM 2.0 (JOSE envelope), W3C DID Core 1.0 and W3C Bitstring Status List v1.0. It also references the ETSI TS 119 602 Trusted List, ETSI EN 319 102-1 PAdES-B-B, HAIP §5.11 Wallet Attestation and the European Digital Identity Wallet framework (eIDAS 2.0, ARF 3.0).