---
title: "Offer a paid service · intentic API"
description: "Wire a paid service into intentic: one JSON endpoint, the signature to verify, what is paid versus refunded, and how pricing in credits works."
url: "https://intentic.dev/developers/services/"
updated: "2026-08-24"
---

Ship

# Offer a paid service

A service is not an extension: no manifest, bundle or repo pointer. It is one HTTPS endpoint that receives metered, signed calls from the platform. The technical bar is verifying a webhook, and admission is mechanical: pass the checks and you are listed, with nobody in the loop.

**On this page (7 sections)**

- [What a service is for](#what-a-service-is-for)
- [The shape of a run](#the-shape-of-a-run)
- [Verify the signature](#verify-the-signature)
- [What's paid, what refunds](#whats-paid-what-refunds)
- [Pricing & the split](#pricing-the-split)
- [Admission & the watch](#admission-the-watch)
- [Listing one, start to finish](#listing-one-start-to-finish)

## What a service is for

Most tools should be free extensions. Reach for a **service** only when every run costs *you* real money: a paid data API, heavy compute, a licensed corpus. Giving it away per run would mean paying to be used. This page is the provider's side; what a run looks like from the member's seat is on [Earn](https://intentic.dev/earn/).

| | Extension | Service |
| --- | --- | --- |
| **What it is** | Code of yours, run in someone's sandbox | One HTTPS endpoint, run by you |
| **Every run** | Free, forever | Costs credits, priced by you |
| **What ships** | A repo pointer, pinned to a commit | Nothing. A signed call reaches your endpoint |
| **You're paid** | Donations from the monthly pool | A share of every paid run |

## The shape of a run

The platform is the intermediary: it charges the member's credits atomically, forwards their JSON to you with a signature, and relays what you send back **as you send it**. Your answer is a stream: NDJSON, one event per line.`status` lines while you work (each replaces the last, a progress label rather than a log) and exactly one`result` whose `data` is the answer, which ends the run. The status lines appear live on the run's card in the member's chat; the result goes to their agent. A run has five minutes and 2 MB of stream to finish in, and richer event kinds will join the vocabulary as chat learns to render them. A service written today streams the two above.

*The platform is the only party that holds the money or learns who is asking. You get a signed call and stream events back. The stream is your whole side of it, never the member's identity or their wallet.*

1. 1

 The **member** clicks approve on the run's card. Their agent cannot spend without that click.
2. 2

 The **platform** charges the credits and forwards one **signed** call to your endpoint.
3. 3

 You **stream events back**: `status` lines while you work, then one `result` that ends the run. Up to five minutes.
4. 4

 The platform **relays each event live**: status onto the member's card, the result to their agent.
5. 5

 The **ledger settles** with a receipt: paid. No result (a 5xx, a timeout, a dead stream) and the **charge is reversed**, costing the member nothing.

One run, on the wire

```http
POST /your-endpoint HTTP/1.1
content-type: application/json
x-intentic-timestamp: 1791234567
x-intentic-signature: 3f1a9c… # HMAC-SHA256 over "{timestamp}.{body}"

{ "query": "which subreddits fit a self-hosted agent workspace?" }

HTTP/1.1 200 OK
content-type: application/x-ndjson

{ "event": "status", "text": "Searching 240 communities…" }
{ "event": "status", "text": "Ranking the 12 that fit…" }
{ "event": "result", "data": { "communities": [ … ], "confidence": 0.82 } }
```

## Verify the signature

What you get instead of issuing API keys: every forwarded call carries `x-intentic-timestamp` and `x-intentic-signature`, an HMAC over `{timestamp}.{body}` with the secret you were issued at onboarding, using the same scheme as Stripe webhooks. Verify it and drop everything else, and nobody but the platform can run up your upstream bill. The timestamp check makes a replayed capture die of old age.

The whole verification

```typescript
import { createHmac, timingSafeEqual } from "node:crypto";

// x-intentic-signature = HMAC-SHA256(secret, "{timestamp}.{body}"); reject anything older than a few minutes.
export function verifyIntenticSignature(body: string, timestamp: string, signature: string, secret: string): boolean {
 if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
 const expected = createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
 const a = Buffer.from(signature, "utf8");
 const b = Buffer.from(expected, "utf8");
 return a.length === b.length && timingSafeEqual(a, b);
}
```

## What's paid, what refunds

The line is **whether a `result` arrived**, not whether the caller liked it. A 4xx refusal is still an answer: "your query was malformed" is the service serving exactly what was asked, and it's paid.

| Your response | The member | You |
| --- | --- | --- |
| `2xx`: a stream ending in its `result` | pays | paid |
| `4xx`: "your query was malformed" | pays | paid |
| `2xx`: a stream that dies before its `result`, or isn't the event format | refunded | not paid |
| `5xx`: your endpoint failed | refunded | not paid |
| Timeout (5 min) or dead socket | refunded | not paid |

Credits are spent atomically before the call, so a refund is immediate. The receipt on the member's card says the run cost nothing. Validate loudly: a clear `4xx` is a real answer, and the paid, honest thing to serve.

## Pricing & the split

You publish one number, **credits per run**, shown on every surface before the run. Price your real cost plus margin, not a teaser; a refusal for lack of credits tells the member what's left and when it resets.

| You set | **Credits per run**: one number, shown before every run. |
| --- | --- |
| You keep | **90%** of every spent credit's dollar value. |
| Public | Your run counts, credits and earnings, per service, month by month, for anyone to read. |

[Earn](https://intentic.dev/earn/) explains what a credit is worth and why the model is unfarmable.

## Admission & the watch

A service has no code to audit (none of yours ships to anyone), so admission asks the only three questions that can be answered mechanically, and answers them without a person. **There is no review queue and no waiting list.** Pass all three and your listing is live immediately.

| Gate | What it checks |
| --- | --- |
| **Identity** | You hold a proved publisher name (a [registry name](https://intentic.dev/developers/publish/) proved from one of its repositories, or **your own domain**, proved by serving a challenge at `/.well-known/intentic-claim`) and payouts are connected. A listing has to be payable before it can be offered. A domain publisher's endpoints must live on that domain or its subdomains, so the name on the card and the host serving the runs are the same party. |
| **Conformance** | A live probe of your endpoint: one correctly signed call that must serve, plus a forged signature and an expired timestamp that must both be *refused*. |
| **Listing rules** | A public https endpoint, a price inside 1–200 credits, and bounded name and description carrying no reserved words. |

The two refusal checks matter as much as the serving one, and they are there for *your* sake: an endpoint that answers a forged call is one anyone on the internet can bill against your own upstream costs. We will not list it.

A new listing goes live **on probation**: capped at 25 credits per run and badged as new on every card a member sees, which is the honest form of "admitted by machine, not vouched for". It graduates after 50 served runs. After that the watch is behavioral, because with a service behavior is the artifact:

| What's watched | How it works |
| --- | --- |
| **Every run** | Public on the ledger, month by month. A track record neither of us can dress up. |
| **Failure rate** | Above 20% of your last 20 runs failing to answer, the listing is suspended automatically. Those runs were all refunded, so it cost members nothing. |
| **Liveness** | Quiet listings are re-probed; 3 failed checks in a row suspends one, so a service that died stops being offered before a member finds out by clicking. |
| **Disputes** | Almost none to have: "no answer, no charge" is the platform's code, not a support queue. |

A suspension is never a deletion: the row, its runs and its earnings stay, the reason is stated, and a fixed endpoint can publish again, back through probation, because a new endpoint is a new thing to trust. Prices move once every 24 hours, and one account may hold 5 live listings.

Throughout, the member keeps their guard: the agent only *discovers* and offers, and nothing is spent until the owner clicks approve on the run's card, a gate the member's own sandbox enforces, not an etiquette the model is trusted with. Discovery is the agent's job, admission is the platform's, spending stays the member's.

## Listing one, start to finish

All of it happens on **Settings → Offer a service** in your own workspace. The screen states the live thresholds the platform is applying, so the numbers you plan against are the numbers that decide.

1. 1

 Prove a publisher name and connect payouts, on **Settings → Getting paid**. No extension in the registry? Claim your domain instead. The screen hands you one line to serve at its well-known path.
2. 2

 Create a draft listing. You get your signing secret once, at that moment. We keep only an encrypted copy and can never read it back to you.
3. 3

 Deploy your endpoint and run the health check. It reports all three probes, passed or not, so a rejection tells you exactly what to fix.
4. 4

 Publish. You are live on probation the same second. No queue, no approval, nobody emailed.

The catalog's own `demo-research` service is the living reference: its upstream is run by the platform and verifies exactly the signature on this page, so what this page documents is what the forward actually sends. Its request also picks the outcome, test-card style: `scenario` of `ok`, `slow`, `refuse` (a paid 4xx),`fail` (a refunded 5xx) or `broken` (a stream that dies without its result, refunded), with`paceMs` setting the stream's tempo. Every settlement a member's card can show is reproducible on demand. And there is runnable starter code: [the example provider](https://github.com/intentic/intentic/tree/HEAD/_platform/example-provider) is this whole page as one dependency-free file, kept honest by the platform's own conformance suite, which drives the real admission probe and the real metered forward against it.

A listing publishes a **sample request**: a body your service really answers. It does double duty: it is what the health check sends, so the probe tests a call you know works rather than one we invented, and members' agents read it as the worked example of your request shape. Stuck on any of it, or think a rule is wrong? [Discord](https://discord.gg/3veuzYp32T) is where that conversation happens. It is just no longer the thing standing between you and a listing.

## Related pages

- [Earn](https://intentic.dev/earn/): the economy your price plugs into, and the chat flow your service is offered in.
- [Publish & registries](https://intentic.dev/developers/publish/): shipping a free, unmetered extension instead.

More in Ship

[Previous ← Maintain & grow](https://intentic.dev/developers/maintain/)
