An autonomous CROO Agent Protocol (CAP) provider that turns receipts and invoices (images, PDFs, ZIPs, or shared cloud-folder links) into clean, accounting-ready exports — Excel / CSV / JSON for JP / US / UK / AU, plus QuickBooks and Xero formats. Buyers call the agent on-chain, pay in USDC on Base, and receive structured, downloadable outputs back through CROO.
CROO Agent Hackathon submission — track: Tools for other CAP builders / Data & Verification Agents.
Live on the CROO Agent Store: https://agent.croo.network/agents/773d1bcf-a2e2-411a-a685-b1b550ead75c
- What it does
- Hackathon submission checklist
- Architecture
- CROO / CAP integration (SDK methods used)
- Services offered
- Agent chat mode (
invoice_agent_chat) - Key contract details
- Environment variables
- Local setup
- Local testing
- Docker
- Deploy to Azure Container Apps
- Security notes
- License
This agent runs as a long-lived CROO provider. It listens for on-chain negotiations and paid orders over the CROO WebSocket, executes the requested service, and delivers the results back to the buyer. It exposes four services:
| Service | Purpose |
|---|---|
echo_test |
Connectivity / health check for the CAP round trip. |
invoice_analyze |
One-shot: submit files, get structured invoice data + exports. |
invoice_agent_analyze |
Analyze with agent-style parameter resolution. |
invoice_agent_chat |
Conversational — keeps history, asks for missing info, then runs the job. |
Under the hood the provider forwards work to an Azure Functions Invoice Analyzer backend (create job → poll → fetch outputs), then mirrors the output files into CROO storage so buyers get stable download links instead of short-lived SAS URLs.
- Open-source repository with a permissive license (MIT — see LICENSE).
- README with setup instructions, SDK methods used, and integration notes (this file).
- Built on CAP — callable on-chain, settles in USDC on Base via
@croo-network/sdk. - Listed on the CROO Agent Store (Base mainnet): https://agent.croo.network/agents/773d1bcf-a2e2-411a-a685-b1b550ead75c
- Demo video (≤ 5 min): add your video URL here.
- BUIDL submitted on DoraHacks: https://dorahacks.io/hackathon/croo-hackathon
Replace the placeholders above with your live links before final submission.
CROO (buyer + on-chain settlement)
│ negotiation / order events (WebSocket)
▼
Provider Container (this repo, @croo-network/sdk)
│ create job → poll status → get outputs
▼
Azure Functions Invoice Analyzer
│ standardized outputs (Excel / CSV / JSON)
▼
Provider mirrors outputs → CROO storage → delivered to buyer
Source layout:
src/index.ts— CROO connection, event subscription, order lifecycle, delivery.src/config.ts— environment-variable validation (zod).src/clients/invoiceAnalyzerClient.ts— Azure Functions Invoice API client.src/clients/azureOpenAiClient.ts— Azure OpenAI / Foundry Responses API client.src/services/conversationStore.ts— conversation state store (memory/ Azuretable).src/services/slotFillingService.ts— intent detection and slot filling.src/handlers/invoiceAnalyzeHandler.ts— direct (one-shot) handler.src/handlers/invoiceAgentChatHandler.ts— conversational agent handler.src/types/*— shared type definitions.src/utils/outputFormats.ts— per-country default output formats.scripts/*— local test harnesses (see Local testing).
The provider is built entirely on @croo-network/sdk.
Key methods used (src/index.ts):
Connect & subscribe
const client = new AgentClient(
{ baseURL: CROO_API_URL, wsURL: CROO_WS_URL, logger },
CROO_API_KEY,
);
const stream = await client.connectWebSocket();
stream.on(EventType.NegotiationCreated, handleNegotiationCreated);
stream.on(EventType.OrderPaid, handleOrderPaid);
stream.onAny(logEvent);Order lifecycle
| SDK method | Used for |
|---|---|
client.acceptNegotiation(negotiationId) |
Accept an incoming negotiation for a supported service. |
client.getOrder(orderId) |
Read the paid order details. |
client.getNegotiation(negotiationId) |
Resolve the negotiated service + requirements. |
client.deliverOrder(orderId, deliverable) |
Deliver the result back to the buyer. |
client.uploadFile(fileName, buffer) |
Upload output files into CROO storage. |
client.getDownloadURL(objectKey) |
Get a stable CROO download URL for delivery. |
client.listNegotiations({ ... }) |
Reconcile pending negotiations on reconnect. |
client.listOrders({ ... }) |
Reconcile paid-but-undelivered orders on reconnect. |
Reliability: a background reconciler periodically calls listNegotiations /
listOrders so no negotiation or paid order is missed if the WebSocket drops.
Negotiation-accept and order-processing are idempotent and guarded with
in-flight sets to avoid double-processing.
The set of supported services is defined in SUPPORTED_SERVICES (src/index.ts):
echo_test, invoice_analyze, invoice_agent_analyze, invoice_agent_chat.
Incoming CROO service_ids are mapped to internal service names via
CROO_SERVICE_ID_MAP, with an optional CROO_DEFAULT_SERVICE fallback.
invoice_agent_chat is a stateful, conversational flow:
- Receives a natural-language
message. - Restores conversation state when a
conversationIdis supplied. - Merges slots:
countryHint,outputFormats,inputFiles. - Detects intent with Azure OpenAI, falling back to a rule-based parser.
- If information is missing:
- returns
status=needs_input - returns
questionsandmissingFields - the caller resends the same
conversationIdwith the answer
- returns
- Only when all required info is present:
createJob → waitForJobCompletion → getJobOutputs- returns
status=completed
Shared cloud links are supported and pre-validated before a job is submitted:
- Auto-retries common share links (SharePoint/OneDrive
download=1, Google Drive direct-download conversion, iClouddownload=1). - SharePoint/OneDrive folder links (
/:f:/...) expand supported files (PDF/JPG/PNG/TIFF/HEIC/ZIP), up to 50 files. - Google Drive folder links (
/drive/folders/...) expand recursively, up to 50 files. - iCloud Drive folder links (
/iclouddrive/...) expand public files, up to 50 files. - If a link cannot be fetched, the flow returns
needs_inputand keeps the conversation open.
Constraints enforced against the Azure Functions backend:
countryHintaccepts onlyJP | US | UK | AU | AUTO(note: UK isUK, notGB).outputFormatsaccepts only:standard_excel,standard_csv,universal_json,japan_expense_excel,quickbooks_csv,xero_csv.- Each
inputFilesentry requires eithersourceUrlorcontentBase64. - If
outputFormatsis empty, the backend defaults tostandard_excel,standard_csv,universal_json. - For JP/US/UK/AU extended exports, specify
outputFormatsexplicitly on the provider side.
Copy .env.example to .env and fill in values.
Required
CROO_API_URLCROO_WS_URLCROO_API_KEY(recommended)CROO_SDK_KEY(legacy fallback; used only whenCROO_API_KEYis unset)INVOICE_API_BASE_URLINVOICE_CREATE_JOB_KEYINVOICE_GET_STATUS_KEYINVOICE_GET_OUTPUTS_KEY
Optional
POLL_INTERVAL_SECONDS(default10)POLL_MAX_ATTEMPTS(default24)DEFAULT_COUNTRY_HINT(defaultJP)
Conversation store
CONVERSATION_STORE=memory|table(defaultmemory)AZURE_STORAGE_CONNECTION_STRING(required whenCONVERSATION_STORE=table)CONVERSATION_TABLE_NAME(required whenCONVERSATION_STORE=table)CONVERSATION_PARTITION_KEY(required whenCONVERSATION_STORE=table)
Azure OpenAI (optional — the agent falls back to rule-based intent detection if unset)
AZURE_OPENAI_ENDPOINTAZURE_OPENAI_API_KEYAZURE_OPENAI_DEPLOYMENTAZURE_OPENAI_API_VERSION(default2024-10-21)AZURE_OPENAI_AUTH_HEADER(api-keyorbearer, defaultapi-key)
Conversation-store modes:
- Local development:
CONVERSATION_STORE=memory. - Azure Container Apps production:
CONVERSATION_STORE=tablerecommended (the table is auto-created if missing).
The Storage Table never persists: contentBase64, SAS-signed downloadUrl,
Function keys, CROO_SDK_KEY, or AZURE_OPENAI_API_KEY.
-
Install dependencies:
npm install
-
Create
.envfrom.env.example. -
Type-check:
npm run typecheck
-
Build:
npm run build
-
Start the provider:
npm run dev
Invoice API only:
npm run test:invoice-local -- ./path/to/file.jpgAgent chat (bypasses CROO, uses a mock invoice client):
npm run test:agent-chatConversational multi-file real-send test:
npm run test:agent-chat-multifile -- "<file1>" "<file2>" "<file3>"Azure OpenAI / Foundry Responses API connectivity:
npm run test:azure-openai- Run
npm run test:azure-openaiwithAZURE_OPENAI_AUTH_HEADER=api-key. - If you get
401, switch toAZURE_OPENAI_AUTH_HEADER=bearer. - Re-run
npm run test:azure-openai. - Keep the working mode, then run
npm run test:agent-chat.
Build:
docker build -t croo-provider-invoice .Run:
docker run --rm --env-file .env croo-provider-invoice-
Create the agent in the CROO Dashboard and copy
CROO_API_KEYfrom the Configure Agent screen into.env. -
Set the Azure Functions / Table Storage / Azure OpenAI values in
.env. -
Deploy:
.\scripts\deploy-azure-containerapps.ps1
-
Check logs:
az containerapp logs show --name ca-croo-invoice-provider --resource-group rg-croo-hackathon --follow
Notes:
-
The deploy script reads
.envfirst and registers secrets as Container Apps secrets. -
CROO_API_KEYandCROO_SDK_KEYreference the same secret. -
To ship a code-only update to an existing environment:
.\scripts\update-azure-containerapps.ps1
-
For a temporary, CROO-independent HTTP test, enable the test endpoint:
.\scripts\update-azure-containerapps.ps1 -EnableTempTestHttp -TestHttpToken "<temporary-token>"
- Endpoint:
https://<container-app-fqdn>/test/invoice-agent-chat - Header:
x-test-token: <temporary-token> - Inspect a conversation:
GET https://<container-app-fqdn>/test/conversations/<conversationId>(requiresx-test-token) - When done, redeploy without
-EnableTempTestHttpto close the public test path.
- Endpoint:
-
With
PROVIDER_ACCESS_MODE=private, requests are rejected unlessALLOWED_BUYER_IDS/ALLOWED_WALLET_ADDRESSESare set appropriately. -
Setting
min replicas=0for cost savings may prevent the always-on CROO WebSocket listener from staying connected. -
Rotate CROO / Azure keys before going to production.
- Never commit Function keys, SDK keys, OpenAI keys, or Storage connection strings.
- Never log
contentBase64. - Never log full SAS-signed
downloadUrls at INFO level. - Never log Function keys, OpenAI keys, or the CROO SDK key.
- Prefer
sourceUrlovercontentBase64for large files.
MIT © 2026 Masaaki Sogabe