2026-03-13 23:21:55 +00:00
|
|
|
|
openapi: 3.1.0
|
|
|
|
|
|
info:
|
|
|
|
|
|
# Do not change the title, if the title changes, the import paths will be broken
|
|
|
|
|
|
title: Api
|
|
|
|
|
|
version: 0.1.0
|
|
|
|
|
|
description: API specification
|
|
|
|
|
|
servers:
|
|
|
|
|
|
- url: /api
|
|
|
|
|
|
description: Base API path
|
|
|
|
|
|
tags:
|
|
|
|
|
|
- name: health
|
|
|
|
|
|
description: Health operations
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
- name: jobs
|
2026-03-18 20:00:24 +00:00
|
|
|
|
description: Payment-gated agent job operations (Mode 1 -- per-job)
|
|
|
|
|
|
- name: sessions
|
|
|
|
|
|
description: Pre-funded session balance mode (Mode 2 -- pay once, run many)
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
- name: demo
|
|
|
|
|
|
description: Free demo endpoint (rate-limited)
|
2026-03-20 02:41:12 +00:00
|
|
|
|
- name: gemini
|
|
|
|
|
|
description: Gemini AI chat and image operations
|
2026-03-13 23:21:55 +00:00
|
|
|
|
paths:
|
|
|
|
|
|
/healthz:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: healthCheck
|
|
|
|
|
|
tags: [health]
|
|
|
|
|
|
summary: Health check
|
|
|
|
|
|
description: Returns server health status
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Healthy
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/HealthStatus"
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
/jobs:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: createJob
|
|
|
|
|
|
tags: [jobs]
|
|
|
|
|
|
summary: Create a new agent job
|
|
|
|
|
|
description: Accepts a request, creates a job row, and issues an eval fee Lightning invoice.
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateJobRequest"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"201":
|
|
|
|
|
|
description: Job created successfully
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateJobResponse"
|
|
|
|
|
|
"400":
|
|
|
|
|
|
description: Invalid request body
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"500":
|
|
|
|
|
|
description: Server error
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
/jobs/{id}:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: getJob
|
|
|
|
|
|
tags: [jobs]
|
|
|
|
|
|
summary: Get job status
|
|
|
|
|
|
description: Returns current job state. Automatically advances the state machine when a pending invoice is found to be paid.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Job status
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/JobStatusResponse"
|
|
|
|
|
|
"404":
|
|
|
|
|
|
description: Job not found
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"500":
|
|
|
|
|
|
description: Server error
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
2026-03-18 19:32:34 +00:00
|
|
|
|
/jobs/{id}/refund:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: claimRefund
|
|
|
|
|
|
tags: [jobs]
|
|
|
|
|
|
summary: Claim a refund for overpayment
|
|
|
|
|
|
description: |
|
|
|
|
|
|
After a job completes, if the actual cost (tokens used + infra + margin) was
|
|
|
|
|
|
less than the work invoice amount, the difference is owed back to the user.
|
|
|
|
|
|
Submit a BOLT11 invoice for exactly `refundAmountSats` to receive the payment.
|
|
|
|
|
|
Idempotent: returns 409 if already paid or if no refund is owed.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ClaimRefundRequest"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Refund sent
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ClaimRefundResponse"
|
|
|
|
|
|
"400":
|
|
|
|
|
|
description: Missing invoice, wrong amount, or invalid BOLT11
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"404":
|
|
|
|
|
|
description: Job not found
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"409":
|
|
|
|
|
|
description: Job not complete, refund already paid, or no refund owed
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"500":
|
|
|
|
|
|
description: Server error (e.g. Lightning payment failure)
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
2026-03-18 20:00:24 +00:00
|
|
|
|
/sessions:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: createSession
|
|
|
|
|
|
tags: [sessions]
|
|
|
|
|
|
summary: Create a pre-funded session
|
|
|
|
|
|
description: |
|
|
|
|
|
|
Opens a new session. Pay the returned Lightning invoice to activate it.
|
|
|
|
|
|
Once active, use the `macaroon` from GET /sessions/:id to authenticate requests.
|
|
|
|
|
|
Deposits: 100–10,000 sats. Sessions expire after 24 h of inactivity.
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateSessionRequest"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"201":
|
|
|
|
|
|
description: Session created -- awaiting deposit payment
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateSessionResponse"
|
|
|
|
|
|
"400":
|
|
|
|
|
|
description: Invalid amount
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"500":
|
|
|
|
|
|
description: Server error
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
/sessions/{id}:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: getSession
|
|
|
|
|
|
tags: [sessions]
|
|
|
|
|
|
summary: Get session status
|
|
|
|
|
|
description: Returns current state, balance, and pending invoice info. Auto-advances on payment.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Session status
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionStatusResponse"
|
|
|
|
|
|
"404":
|
|
|
|
|
|
description: Session not found
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
/sessions/{id}/request:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: submitSessionRequest
|
|
|
|
|
|
tags: [sessions]
|
|
|
|
|
|
summary: Submit a request against a session balance
|
|
|
|
|
|
description: |
|
|
|
|
|
|
Runs eval + work and debits the actual compute cost from the session balance.
|
|
|
|
|
|
Rejected requests still incur a small eval fee. Requires `Authorization: Bearer <macaroon>`.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
security:
|
|
|
|
|
|
- sessionMacaroon: []
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionRequestBody"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Request completed (or rejected)
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionRequestResponse"
|
|
|
|
|
|
"401":
|
|
|
|
|
|
description: Missing or invalid macaroon
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"402":
|
|
|
|
|
|
description: Insufficient balance
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"409":
|
|
|
|
|
|
description: Session not active
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"410":
|
|
|
|
|
|
description: Session expired
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
/sessions/{id}/topup:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: topupSession
|
|
|
|
|
|
tags: [sessions]
|
|
|
|
|
|
summary: Add sats to a session
|
|
|
|
|
|
description: |
|
|
|
|
|
|
Creates a new Lightning invoice to top up the session balance.
|
|
|
|
|
|
Only one pending topup at a time. Paying it resumes a paused session.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
security:
|
|
|
|
|
|
- sessionMacaroon: []
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateSessionRequest"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Topup invoice created
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/TopupSessionResponse"
|
|
|
|
|
|
"400":
|
|
|
|
|
|
description: Invalid amount
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"401":
|
|
|
|
|
|
description: Invalid macaroon
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"409":
|
|
|
|
|
|
description: Session not active/paused, or topup already pending
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
/demo:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: runDemo
|
|
|
|
|
|
tags: [demo]
|
|
|
|
|
|
summary: Free demo (rate-limited)
|
|
|
|
|
|
description: Runs the agent without payment. Limited to 5 requests per IP per hour.
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: request
|
|
|
|
|
|
in: query
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Demo result
|
2026-03-18 21:49:51 -04:00
|
|
|
|
headers:
|
|
|
|
|
|
X-RateLimit-Limit:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Maximum requests allowed per window (always 5)
|
|
|
|
|
|
X-RateLimit-Remaining:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Requests remaining in the current window
|
|
|
|
|
|
X-RateLimit-Reset:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Unix epoch seconds when the rate limit window resets
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/DemoResponse"
|
|
|
|
|
|
"400":
|
|
|
|
|
|
description: Missing or invalid request param
|
2026-03-18 21:49:51 -04:00
|
|
|
|
headers:
|
|
|
|
|
|
X-RateLimit-Limit:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
X-RateLimit-Remaining:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
X-RateLimit-Reset:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"429":
|
|
|
|
|
|
description: Rate limit exceeded
|
2026-03-18 21:49:51 -04:00
|
|
|
|
headers:
|
|
|
|
|
|
X-RateLimit-Limit:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
X-RateLimit-Remaining:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Always 0 when rate limited
|
|
|
|
|
|
X-RateLimit-Reset:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
|
|
|
|
|
"500":
|
|
|
|
|
|
description: Server error
|
2026-03-18 21:49:51 -04:00
|
|
|
|
headers:
|
|
|
|
|
|
X-RateLimit-Limit:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
X-RateLimit-Remaining:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
X-RateLimit-Reset:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/ErrorResponse"
|
2026-03-20 02:41:12 +00:00
|
|
|
|
/gemini/conversations:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: listGeminiConversations
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: List all conversations
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: List of conversations
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: array
|
|
|
|
|
|
items:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiConversation"
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: createGeminiConversation
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: Create a new conversation
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/CreateGeminiConversationBody"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"201":
|
|
|
|
|
|
description: Created conversation
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiConversation"
|
|
|
|
|
|
/gemini/conversations/{id}:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: getGeminiConversation
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: Get conversation with messages
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Conversation with messages
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiConversationWithMessages"
|
|
|
|
|
|
"404":
|
|
|
|
|
|
description: Not found
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiError"
|
|
|
|
|
|
delete:
|
|
|
|
|
|
operationId: deleteGeminiConversation
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: Delete a conversation
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"204":
|
|
|
|
|
|
description: Deleted
|
|
|
|
|
|
"404":
|
|
|
|
|
|
description: Not found
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiError"
|
|
|
|
|
|
/gemini/conversations/{id}/messages:
|
|
|
|
|
|
get:
|
|
|
|
|
|
operationId: listGeminiMessages
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: List messages in a conversation
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: List of messages
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: array
|
|
|
|
|
|
items:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiMessage"
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: sendGeminiMessage
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: Send a message and receive an AI response (SSE stream)
|
|
|
|
|
|
parameters:
|
|
|
|
|
|
- name: id
|
|
|
|
|
|
in: path
|
|
|
|
|
|
required: true
|
|
|
|
|
|
schema:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/SendGeminiMessageBody"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: SSE stream of assistant response chunks
|
|
|
|
|
|
content:
|
|
|
|
|
|
text/event-stream: {}
|
|
|
|
|
|
/gemini/generate-image:
|
|
|
|
|
|
post:
|
|
|
|
|
|
operationId: generateGeminiImage
|
|
|
|
|
|
tags: [gemini]
|
|
|
|
|
|
summary: Generate an image from a text prompt
|
|
|
|
|
|
requestBody:
|
|
|
|
|
|
required: true
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GenerateGeminiImageBody"
|
|
|
|
|
|
responses:
|
|
|
|
|
|
"200":
|
|
|
|
|
|
description: Generated image
|
|
|
|
|
|
content:
|
|
|
|
|
|
application/json:
|
|
|
|
|
|
schema:
|
|
|
|
|
|
$ref: "#/components/schemas/GenerateGeminiImageResponse"
|
2026-03-13 23:21:55 +00:00
|
|
|
|
components:
|
|
|
|
|
|
schemas:
|
|
|
|
|
|
HealthStatus:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
status:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
required:
|
|
|
|
|
|
- status
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
ErrorResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- error
|
|
|
|
|
|
properties:
|
|
|
|
|
|
error:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
InvoiceInfo:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- paymentRequest
|
|
|
|
|
|
- amountSats
|
|
|
|
|
|
properties:
|
|
|
|
|
|
paymentRequest:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
amountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
CreateJobRequest:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- request
|
|
|
|
|
|
properties:
|
|
|
|
|
|
request:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
minLength: 1
|
|
|
|
|
|
CreateJobResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- jobId
|
2026-03-18 21:49:51 -04:00
|
|
|
|
- createdAt
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
- evalInvoice
|
|
|
|
|
|
properties:
|
|
|
|
|
|
jobId:
|
|
|
|
|
|
type: string
|
2026-03-18 21:49:51 -04:00
|
|
|
|
createdAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
description: ISO 8601 timestamp of job creation
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
evalInvoice:
|
|
|
|
|
|
$ref: "#/components/schemas/InvoiceInfo"
|
|
|
|
|
|
JobState:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
enum:
|
|
|
|
|
|
- awaiting_eval_payment
|
|
|
|
|
|
- evaluating
|
|
|
|
|
|
- rejected
|
|
|
|
|
|
- awaiting_work_payment
|
|
|
|
|
|
- executing
|
|
|
|
|
|
- complete
|
|
|
|
|
|
- failed
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
PricingBreakdown:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
description: Cost breakdown shown with the work invoice (estimations at invoice-creation time)
|
|
|
|
|
|
properties:
|
|
|
|
|
|
estimatedCostUsd:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
description: Total estimated cost in USD (token cost + DO infra + margin)
|
|
|
|
|
|
marginPct:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
description: Originator margin percentage applied
|
|
|
|
|
|
btcPriceUsd:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
description: BTC/USD spot price used to convert the invoice to sats
|
|
|
|
|
|
CostLedger:
|
|
|
|
|
|
type: object
|
2026-03-18 19:32:34 +00:00
|
|
|
|
description: Honest post-work accounting stored after the job completes
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
properties:
|
|
|
|
|
|
actualInputTokens:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
actualOutputTokens:
|
|
|
|
|
|
type: integer
|
Task #6: Cost-based work fee pricing with BTC oracle
## New files
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper (ceil, min 1 sat),
5s abort timeout, fallback to BTC_PRICE_USD_FALLBACK env var (default $100k)
- lib/db/migrations/0002_cost_based_pricing.sql: SQL migration artifact adding 6 new columns
to jobs table (estimated_cost_usd, margin_pct, btc_price_usd, actual_input_tokens,
actual_output_tokens, actual_cost_usd); idempotent via ADD COLUMN IF NOT EXISTS
## Modified files
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var overridable),
DO infra amortisation per request, originator margin %, estimateInputTokens/Output by tier,
calculateActualCostUsd() for post-work ledger, async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- lib/db/src/schema/jobs.ts: 6 new real/integer columns; schema pushed to DB
- jobs.ts route: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd; post-work stores actualInputTokens/
actualOutputTokens/actualCostUsd; GET response includes pricingBreakdown and costLedger
with totalTokens (input + output computed field)
- openapi.yaml: PricingBreakdown + CostLedger schemas (with totalTokens) added
- lib/api-zod/src/generated/api.ts: Regenerated with new schemas
- lib/api-client-react/src/generated/api.schemas.ts: Regenerated (PricingBreakdown, CostLedger)
- replit.md: 17 new env vars documented in cost-based pricing section
2026-03-18 19:25:06 +00:00
|
|
|
|
totalTokens:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Sum of actualInputTokens + actualOutputTokens
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
actualCostUsd:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
description: Raw Anthropic token cost (no infra, no margin)
|
2026-03-18 19:32:34 +00:00
|
|
|
|
actualChargeUsd:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
description: What we honestly charged in USD (actual token cost + DO infra + margin)
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
estimatedCostUsd:
|
|
|
|
|
|
type: number
|
2026-03-18 19:32:34 +00:00
|
|
|
|
description: Original estimate used to create the work invoice
|
|
|
|
|
|
actualAmountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Honest sats charge (actual cost converted at the locked BTC price)
|
|
|
|
|
|
workAmountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Amount the user originally paid in sats
|
|
|
|
|
|
refundAmountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Sats owed back to the user (workAmountSats - actualAmountSats, >= 0)
|
|
|
|
|
|
refundState:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
enum: [not_applicable, pending, paid]
|
|
|
|
|
|
description: Lifecycle of the refund for this job
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
marginPct:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
btcPriceUsd:
|
|
|
|
|
|
type: number
|
2026-03-18 19:32:34 +00:00
|
|
|
|
description: BTC/USD price locked at invoice creation time
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
JobStatusResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- jobId
|
|
|
|
|
|
- state
|
2026-03-18 21:49:51 -04:00
|
|
|
|
- createdAt
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
properties:
|
|
|
|
|
|
jobId:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
state:
|
|
|
|
|
|
$ref: "#/components/schemas/JobState"
|
2026-03-18 21:49:51 -04:00
|
|
|
|
createdAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
description: ISO 8601 timestamp of job creation (always present)
|
|
|
|
|
|
completedAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
nullable: true
|
|
|
|
|
|
description: ISO 8601 timestamp of job completion; null when not yet complete
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
evalInvoice:
|
|
|
|
|
|
$ref: "#/components/schemas/InvoiceInfo"
|
|
|
|
|
|
workInvoice:
|
|
|
|
|
|
$ref: "#/components/schemas/InvoiceInfo"
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
pricingBreakdown:
|
|
|
|
|
|
$ref: "#/components/schemas/PricingBreakdown"
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
reason:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
result:
|
|
|
|
|
|
type: string
|
Task #6: Cost-based work fee pricing with BTC oracle
- btc-oracle.ts: CoinGecko BTC/USD fetch (60s cache), usdToSats() helper,
fallback to BTC_PRICE_USD_FALLBACK env var (default $100k), 5s abort timeout
- pricing.ts: Full rewrite — per-model token rates (Haiku/Sonnet, env-var
overridable), DO infra amortisation, originator margin %, estimateInputTokens(),
estimateOutputTokens() by request tier, calculateActualCostUsd() for post-work ledger,
async calculateWorkFeeSats() → WorkFeeBreakdown
- agent.ts: WorkResult now includes inputTokens + outputTokens from Anthropic usage;
workModel/evalModel exposed as readonly public; EVAL_MODEL/WORK_MODEL env var support
- jobs.ts: Work invoice creation calls pricingService.calculateWorkFeeSats() async;
stores estimatedCostUsd/marginPct/btcPriceUsd on job; after executeWork stores
actualInputTokens/actualOutputTokens/actualCostUsd; GET response includes
pricingBreakdown (awaiting_work_payment) and costLedger (complete)
- lib/db/src/schema/jobs.ts: 6 new real/integer columns for cost tracking; schema pushed
- openapi.yaml: PricingBreakdown + CostLedger schemas added to JobStatusResponse
- replit.md: 17 new env vars documented in Cost-based work fee pricing section
2026-03-18 19:20:34 +00:00
|
|
|
|
costLedger:
|
|
|
|
|
|
$ref: "#/components/schemas/CostLedger"
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
errorMessage:
|
|
|
|
|
|
type: string
|
2026-03-18 20:00:24 +00:00
|
|
|
|
SessionState:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
enum: [awaiting_payment, active, paused, expired]
|
|
|
|
|
|
SessionInvoiceInfo:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
paymentRequest:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
amountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
paymentHash:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
description: Only present in stub/dev mode
|
|
|
|
|
|
CreateSessionRequest:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [amount_sats]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
amount_sats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
description: Deposit amount (100–10,000 sats)
|
|
|
|
|
|
minimum: 100
|
|
|
|
|
|
maximum: 10000
|
|
|
|
|
|
CreateSessionResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [sessionId, state, invoice]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
sessionId:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
state:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionState"
|
|
|
|
|
|
invoice:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionInvoiceInfo"
|
|
|
|
|
|
SessionStatusResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [sessionId, state, balanceSats]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
sessionId:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
state:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionState"
|
|
|
|
|
|
balanceSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
minimumBalanceSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
macaroon:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
description: Bearer token for authenticating requests; present when active or paused
|
|
|
|
|
|
expiresAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
invoice:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionInvoiceInfo"
|
|
|
|
|
|
description: Present when state is awaiting_payment
|
|
|
|
|
|
pendingTopup:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionInvoiceInfo"
|
|
|
|
|
|
description: Present when a topup invoice is outstanding
|
|
|
|
|
|
SessionRequestBody:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [request]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
request:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
minLength: 1
|
|
|
|
|
|
SessionCostBreakdown:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
properties:
|
|
|
|
|
|
evalSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
workSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
totalSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
btcPriceUsd:
|
|
|
|
|
|
type: number
|
|
|
|
|
|
SessionRequestResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [requestId, state, debitedSats, balanceRemaining]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
requestId:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
state:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
enum: [complete, rejected, failed]
|
|
|
|
|
|
result:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
reason:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
errorMessage:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
debitedSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
balanceRemaining:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
cost:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionCostBreakdown"
|
|
|
|
|
|
TopupSessionResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [sessionId, topup]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
sessionId:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
topup:
|
|
|
|
|
|
$ref: "#/components/schemas/SessionInvoiceInfo"
|
2026-03-18 19:32:34 +00:00
|
|
|
|
ClaimRefundRequest:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- invoice
|
|
|
|
|
|
properties:
|
|
|
|
|
|
invoice:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
description: BOLT11 invoice for exactly refundAmountSats
|
|
|
|
|
|
ClaimRefundResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- ok
|
|
|
|
|
|
- refundAmountSats
|
|
|
|
|
|
- paymentHash
|
|
|
|
|
|
- message
|
|
|
|
|
|
properties:
|
|
|
|
|
|
ok:
|
|
|
|
|
|
type: boolean
|
|
|
|
|
|
refundAmountSats:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
paymentHash:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
message:
|
|
|
|
|
|
type: string
|
Task #3: MVP API — payment-gated jobs + demo endpoint
OpenAPI spec (lib/api-spec/openapi.yaml)
- Added POST /jobs, GET /jobs/{id}, GET /demo endpoints
- Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse,
InvoiceInfo, JobState, DemoResponse, ErrorResponse
- Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc.
Jobs router (artifacts/api-server/src/routes/jobs.ts)
- POST /jobs: validates body, creates LNbits eval invoice, inserts job +
invoice in a DB transaction, returns { jobId, evalInvoice }
- GET /jobs/:id: fetches job, calls advanceJob() helper, returns state-
appropriate payload (eval/work invoice, reason, result, errorMessage)
- advanceJob() state machine:
- awaiting_eval_payment: checks LNbits, atomically marks paid + advances
state via optimistic WHERE state='awaiting_eval_payment'; runs
AgentService.evaluateRequest, branches to awaiting_work_payment or rejected
- awaiting_work_payment: same pattern for work invoice, runs
AgentService.executeWork, advances to complete
- Any agent/LNbits error transitions job to failed
Demo router (artifacts/api-server/src/routes/demo.ts)
- GET /demo?request=...: in-memory rate limiter (5 req/hour per IP)
- Explicit guard for missing request param (coerce.string() workaround)
- Calls AgentService.executeWork directly, returns { result }
Dev router (artifacts/api-server/src/routes/dev.ts)
- POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory
- Only mounted when NODE_ENV !== 'production'
Route index updated to mount all three routers
replit.md: documented full curl flow with all 6 steps, demo endpoint,
and dev stub-pay trigger
End-to-end verified with curl:
- Full flow: create → eval pay → evaluating → work pay → executing → complete
- Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
|
|
|
|
DemoResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required:
|
|
|
|
|
|
- result
|
|
|
|
|
|
properties:
|
|
|
|
|
|
result:
|
|
|
|
|
|
type: string
|
2026-03-20 02:41:12 +00:00
|
|
|
|
GeminiConversation:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [id, title, createdAt]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
id:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
title:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
createdAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
GeminiMessage:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [id, conversationId, role, content, createdAt]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
id:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
conversationId:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
role:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
content:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
createdAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
GeminiConversationWithMessages:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [id, title, createdAt, messages]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
id:
|
|
|
|
|
|
type: integer
|
|
|
|
|
|
title:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
createdAt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
format: date-time
|
|
|
|
|
|
messages:
|
|
|
|
|
|
type: array
|
|
|
|
|
|
items:
|
|
|
|
|
|
$ref: "#/components/schemas/GeminiMessage"
|
|
|
|
|
|
CreateGeminiConversationBody:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [title]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
title:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
SendGeminiMessageBody:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [content]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
content:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
model:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
description: "Gemini model override (default: gemini-3-flash-preview)"
|
|
|
|
|
|
GenerateGeminiImageBody:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [prompt]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
prompt:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
GenerateGeminiImageResponse:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [b64_json, mimeType]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
b64_json:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
mimeType:
|
|
|
|
|
|
type: string
|
|
|
|
|
|
GeminiError:
|
|
|
|
|
|
type: object
|
|
|
|
|
|
required: [error]
|
|
|
|
|
|
properties:
|
|
|
|
|
|
error:
|
|
|
|
|
|
type: string
|
2026-03-18 20:00:24 +00:00
|
|
|
|
securitySchemes:
|
|
|
|
|
|
sessionMacaroon:
|
|
|
|
|
|
type: http
|
|
|
|
|
|
scheme: bearer
|
2026-03-20 02:41:12 +00:00
|
|
|
|
description: "Session macaroon issued when a session activates. Pass as `Authorization: Bearer <macaroon>`."
|