Quinn is a thin TypeScript A2A gateway built on samsar-js for Samsar text-to-video generation. It gives agents an account-scoped workflow to create a billing account, purchase credits, submit a prompt and duration, monitor the render, and retrieve the completed video URL.
Quinn fixes the generation pipeline to Qwen 3.7, Wan 2.7 Pro, and Happy Horse 1.1. It omits every other generation setting so Samsar can apply its defaults.
- Creates isolated Samsar external-user accounts and returns a scoped account key.
- Creates credit checkout sessions and exposes account-specific payment status and balances.
- Accepts text-to-video requests through A2A 1.0 JSON-RPC or HTTP+JSON.
- Polls and lists account-owned render tasks and returns completed video URLs as A2A artifacts.
- Prevents callers from changing Quinn's fixed model pipeline; additional generation settings are ignored and not forwarded.
- Keeps no local account, billing, or task database; Samsar remains the system of record.
flowchart LR
Agent["A2A agent"] -->|"Create account, fund, submit, poll"| Quinn["Quinn"]
Quinn -->|"Platform key for provisioning"| Samsar["Samsar API"]
Quinn -->|"Scoped external-user key"| Samsar
Samsar --> Pipeline["Fixed Quinn video pipeline"]
Pipeline -->|"Task status and video URL"| Quinn
Quinn --> Agent
Quinn holds one Samsar platform API key on the server. POST /accounts provisions a high-entropy external-user identity and returns its scoped account key. Billing, generation, status, and task-list operations use that scoped key, keeping credits and renders isolated to the agent's account.
The account key is the account credential. Quinn does not persist it or provide a recovery endpoint, so clients must store it securely when it is returned.
| Stage | Model key | Model |
|---|---|---|
| Inference | QWEN3.7 |
Qwen 3.7 |
| Text to image | WAN2.7PRO |
Wan 2.7 Pro |
| Image to video | HAPPYHORSEI2V |
Happy Horse 1.1 I2V |
For text-to-video generation, the generation input contains exactly these five fields:
{
"prompt": "A cinematic launch film for an electric motorcycle at night",
"duration": 10,
"inference_model": "QWEN3.7",
"image_model": "WAN2.7PRO",
"video_model": "HAPPYHORSEI2V"
}Callers control only prompt and duration. All other generation fields are omitted and use Samsar defaults.
- Git
- Node.js 20 or newer with npm
- A Samsar platform API key
Clone and install Quinn:
git clone https://github.com/samsarone/Quinn.git
cd Quinn
npm ci
cp .env.example .envAdd the platform credential to .env:
SAMSAR_API_KEY=your-platform-api-key
PUBLIC_BASE_URL=http://localhost:8080Start the development server:
npm run devQuinn listens on http://localhost:8080 by default. Confirm it is running:
curl -sS http://localhost:8080/healthAccount creation is outside A2A because the returned credential authenticates the agent's later A2A and billing requests.
curl -sS -X POST http://localhost:8080/accounts \
-H 'content-type: application/json' \
-d '{
"display_name": "Launch Video Agent",
"external_agent_id": "agent-123",
"metadata": { "team": "creative" }
}'The response includes the new account and its credential. This is an abbreviated example: Quinn generates the account ID at runtime, while Samsar supplies the scoped credential and account data.
{
"account": {
"id": "<account-id>",
"provider": "quinn"
},
"credentials": {
"accountKey": "<account-key>",
"authorization": "Bearer <account-key>",
"headerName": "x-quinn-account-key"
}
}Store the returned key for the remaining examples:
export QUINN_ACCOUNT_KEY="<account-key>"Caller-supplied external IDs can provide attribution, but none controls Quinn's generated account identity or ownership boundary.
Create a checkout for a positive integer number of credits:
curl -sS -X POST http://localhost:8080/billing/credits/purchases \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY" \
-H 'content-type: application/json' \
-H 'Idempotency-Key: purchase-500-credits-1' \
-d '{ "credits": 500 }'Open the returned url to complete checkout, then poll with an externalPaymentId, checkoutSessionId, paymentIntentId, or setupIntentId returned by Samsar:
curl -sS 'http://localhost:8080/billing/payments/status?checkoutSessionId=<checkout-session-id>' \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY"Payment state progresses from pending to succeeded or failed. Credits are applied upstream after payment succeeds.
Check the account balance:
curl -sS http://localhost:8080/billing/credits \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY"Send an A2A 1.0 JSON-RPC request with a prompt and duration. Prompts may contain up to 4,000 characters; duration must be from 10 through 240 seconds.
curl -sS -X POST http://localhost:8080/a2a \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY" \
-H 'A2A-Version: 1.0' \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "submit-1",
"method": "SendMessage",
"params": {
"configuration": { "returnImmediately": true },
"metadata": { "skill": "text_to_video" },
"message": {
"role": "ROLE_USER",
"messageId": "launch-film-1",
"parts": [{
"data": {
"input": {
"prompt": "A cinematic launch film for an electric motorcycle at night",
"duration": 10
}
}
}]
}
}
}'Save result.task.id from the response. messageId is required, may contain at most 200 characters and no control characters, and is also forwarded as the Samsar idempotency key.
returnImmediately: true returns the submitted task for polling. If omitted or set to false, Quinn follows A2A's blocking behavior and polls until the task reaches a returnable state or A2A_BLOCKING_WAIT_TIMEOUT_MS expires.
curl -sS -X POST http://localhost:8080/a2a \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY" \
-H 'A2A-Version: 1.0' \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "poll-1",
"method": "GetTask",
"params": { "id": "<task-id>" }
}'Continue polling while the task is TASK_STATE_SUBMITTED or TASK_STATE_WORKING. Stop on TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, or TASK_STATE_REJECTED; handle TASK_STATE_INPUT_REQUIRED, TASK_STATE_AUTH_REQUIRED, and TASK_STATE_UNSPECIFIED explicitly. Every task includes a samsar-response artifact with the raw status payload. When Samsar returns an output URL, a completed task also includes a result-video artifact with one or more video/mp4 URL parts.
The equivalent HTTP+JSON request is:
curl -sS 'http://localhost:8080/tasks/<task-id>' \
-H "Authorization: Bearer $QUINN_ACCOUNT_KEY" \
-H 'A2A-Version: 1.0'The public Agent Card is served at /.well-known/agent-card.json. Authenticated operations accept either Authorization: Bearer <account-key> or x-quinn-account-key: <account-key>.
Send A2A-Version: 1.0 on every A2A operation. A missing version header is interpreted as A2A 0.3 and rejected because Quinn exposes only version 1.0.
| Operation | JSON-RPC method | HTTP+JSON | Behavior |
|---|---|---|---|
| Send message | SendMessage |
POST /message:send |
Runs a Quinn skill and returns a message or task. |
| Get task | GetTask |
GET /tasks/:id |
Gets current render state and artifacts. |
| List tasks | ListTasks |
GET /tasks |
Lists recent tasks owned by the account. |
| Cancel task | CancelTask |
POST /tasks/:id:cancel |
Returns TaskNotCancelable; external T2V cancellation is not available upstream. |
Follow-up messages containing an existing taskId, streaming, push notifications, task subscription, and extended Agent Cards are not supported. Use a new SendMessage request to create work and GetTask to poll it.
| Skill | Input | Result |
|---|---|---|
text_to_video |
prompt, duration |
Starts a render and returns an A2A task. |
get_credits |
none | Returns the current account balance in an A2A message. |
purchase_credits |
positive integer credits |
Returns checkout details in an A2A message. |
get_payment_status |
A payment or checkout identifier | Returns the account-scoped payment status in an A2A message. |
Atlas-compatible aliases are accepted, including create_video_from_text, create_credits_recharge, and billing_get_payment_status.
| Method | Endpoint | Auth | Purpose |
|---|---|---|---|
GET |
/ |
none | Service metadata and primary links. |
GET |
/health |
none | Liveness check. |
GET |
/.well-known/agent-card.json |
none | A2A discovery. |
POST |
/accounts |
none | Creates an external-user account and scoped credential. |
GET |
/billing/credits |
account key | Gets the current credit balance. |
POST |
/billing/credits/purchases |
account key | Creates a credit checkout. |
GET |
/billing/payments/status |
account key | Gets checkout or payment status. |
POST |
/a2a |
account key | A2A 1.0 JSON-RPC endpoint. |
POST |
/message:send |
account key | A2A HTTP+JSON Send Message. |
GET |
/tasks/:id |
account key | A2A HTTP+JSON Get Task. |
GET |
/tasks |
account key | Lists recent account-owned T2V tasks. |
POST |
/tasks/:id:cancel |
account key | Reports that the task cannot be canceled. |
Account, billing, and authenticated A2A operation responses use Cache-Control: no-store. The public Agent Card is cached for five minutes. Account creation and credit checkout endpoints also have configurable per-IP rate limits.
| Variable | Required | Default | Purpose |
|---|---|---|---|
SAMSAR_API_KEY |
yes | — | Server-side Samsar platform credential. |
SAMSAR_API_BASE_URL |
no | https://api.samsar.one/v1 |
Samsar SDK base URL; root and /v2 URLs are normalized to /v1. |
PUBLIC_BASE_URL |
production | http://localhost:$PORT |
Public origin advertised by the Agent Card. |
PORT |
no | 8080 |
HTTP listening port. |
SAMSAR_REQUEST_TIMEOUT_MS |
no | 60000 |
Upstream request timeout in milliseconds. |
A2A_POLL_INTERVAL_MS |
no | 2000 |
Poll interval for blocking SendMessage requests. |
A2A_BLOCKING_WAIT_TIMEOUT_MS |
no | 900000 |
Maximum blocking A2A wait in milliseconds. |
JSON_BODY_LIMIT |
no | 1mb |
Maximum accepted JSON body size. |
RATE_LIMIT_WINDOW_MS |
no | 900000 |
Window used by self-service rate limits. |
ACCOUNT_CREATION_RATE_LIMIT_MAX |
no | 10 |
Account creations allowed per client IP and window. |
CREDIT_PURCHASE_RATE_LIMIT_MAX |
no | 20 |
Checkout creations allowed per client IP and window. |
TRUST_PROXY_HOPS |
no | 0 |
Exact number of trusted reverse-proxy hops. |
QUINN_ACCOUNT_PROVIDER |
no | quinn |
Samsar external-user provider namespace. |
AGENT_CARD_DOCUMENTATION_URL |
no | omitted | Public documentation URL placed in the Agent Card. |
Build and run the production image:
docker build -t quinn .
docker run --rm -p 8080:8080 \
-e SAMSAR_API_KEY="$SAMSAR_API_KEY" \
-e PUBLIC_BASE_URL="https://quinn.example.com" \
quinnThe runtime image uses Node.js 20 and runs as the unprivileged node user.
| Command | Purpose |
|---|---|
npm run dev |
Starts Quinn in watch mode using .env. |
npm run typecheck |
Type-checks source and tests. |
npm test |
Runs the Node test suite. |
npm run build |
Compiles production output to dist/. |
npm run check |
Runs type-checking, tests, and the production build. |
npm start |
Starts the compiled server from dist/. |
Before submitting a change:
npm run check- Keep
SAMSAR_API_KEY, Quinn account keys,.env, and checkout identifiers out of source control, browser bundles, logs, and Agent Cards. - Serve Quinn over HTTPS in production.
- Persist the account key in a secure secret store when
POST /accountsreturns it. - For an A2A render, use one
messageIdper logical operation and reuse it when retrying that operation. - For an HTTP credit checkout, use one
Idempotency-Keyper logical operation and reuse it when retrying that operation. - Set
TRUST_PROXY_HOPSto the exact proxy count; do not enable broad proxy trust. - Quinn's built-in limits are per process. Multi-instance deployments should add shared account/IP quotas at the API gateway.
| Resource | Link |
|---|---|
| Samsar API documentation | docs.samsar.one |
| Samsar V2 API | docs.samsar.one/v2 |
| External-user accounts | docs.samsar.one/external-users |
| Credits and billing | docs.samsar.one/credits |
| A2A 1.0 specification | a2a-protocol.org |
| A2A normative protobuf | a2aproject/A2A |
Quinn is available under the MIT License.