---
title: "Building Ouro services with a coding agent"
description: "Have Cursor or Claude Code turn a model repository into a live Ouro API, then register, test, and announce it without leaving the editor."
date: "2026-09-22"
last_updated: "2026-09-22"
---

Putting a model on Ouro used to mean an afternoon of plumbing: read the repo,
wrap inference in an API, deploy it, write the OpenAPI extensions, register the
service, and test it by hand. A coding agent connected to Ouro can do the whole
loop. It reads the model's code, writes the service, deploys it, registers it
with the [MCP server](/docs/developers/mcp), runs it on a real file, and
drafts the announcement.

What the agent doesn't know out of the box is how Ouro services are built:
which headers Ouro sends, how to declare input and output assets, and when a
route should be async. This guide shows how to give it that knowledge with a
skill, then walks the loop from a model repository to a live, tested route.

## Before you start

- **A coding agent connected to Ouro.** Follow [Using Ouro in Cursor and
  Claude](/guides/using-ouro-in-cursor-and-claude) first.
- **A place to deploy.** The examples use [Modal](https://modal.com), which
  gives you GPUs and a public HTTPS URL per app. Any host that serves an
  OpenAPI spec works.
- **A model worth sharing.** Open weights, an inference script, and a license
  that allows redistribution.

## Teach the agent Ouro's conventions

A skill is a markdown file with instructions for one kind of task. The agent
reads it when a request matches its description, so you don't repeat the
conventions in every prompt.

The [Ouro skills repository](https://github.com/ourofoundation/skills) has one
for this job: `matsci-modal-app` builds Modal apps for open materials-science
models, with Ouro file inputs and outputs, webhook callbacks for long jobs, and
progress logging. It's how most of the models in
[#materials-science](/teams/materials-science) got there. Install it for your
agent:

```bash
# Any agent, with the skills CLI
npx skills add ourofoundation/skills

# Cursor, for every project
git clone https://github.com/ourofoundation/skills.git ~/.cursor/skills/ouro

# Claude Code, for this project
git clone https://github.com/ourofoundation/skills.git .claude/skills/ouro
```

If your models aren't materials science, write your own skill. It doesn't need
to be long. What matters is the handful of rules the agent would otherwise get
wrong:

```markdown title="skills/ouro-service/SKILL.md"
---
name: ouro-service
description: Build and deploy an HTTP API for a model and publish it as an Ouro
  service. Use when wrapping a model or script as an Ouro route.
---

# Ouro services

- Build with FastAPI. Generate the spec with `get_custom_openapi` and declare
  assets with `ouro_field` from `ouro.utils`.
- Declare file and dataset inputs with keyed `x-ouro-input-assets`, keyed by
  the request body field that receives them, with exact `file_extensions`.
- Declare large outputs with `x-ouro-output-assets` and return them as base64
  payloads with `org_id` and `team_id` from the `ouro-route-org-id` and
  `ouro-route-team-id` headers.
- Keep the JSON response a small summary: scalars, counts, and verdicts.
- Anything that can take over five minutes returns 202 and reports back through
  the `ouro-webhook-url` header.
- Log milestones with `action.log()`. Return 400s that name the bad field.
- Give every optional parameter a default, and describe when to use each route.
```

Each rule links to a guide if you want the reasoning:
[route input and output assets](/guides/route-input-output-assets),
[designing routes for agents](/guides/agent-friendly-routes), and
[long-running APIs](/guides/long-running-apis).

## The loop

Each step below is one prompt. You'll review code and results between steps,
the way you would with a colleague.

### 1. Understand the model

> Read the README and inference code in `github.com/example/crystal-model`.
> What does it take as input, what does it return, what hardware does it need,
> and how long does one prediction take?

That last question decides the most important design choice. A prediction that
finishes in seconds can return directly. One that takes twenty minutes has to
be async, or the call times out.

### 2. Build the service

> Build a Modal app for it under `apps/crystal-model/` following the Ouro
> service skill. One route that takes a CIF file and returns predicted
> properties, plus the relaxed structure as an output file.

Check the declarations before anything else. They're what let Ouro resolve a
caller's file into your request body and save your outputs as assets:

```python showLineNumbers
@web_app.post(
    "/predict",
    summary="Predict formation energy and band gap",
    description="Predicts properties of a crystal structure. Use to screen "
    "candidates before running DFT.",
)
@ouro_field(
    "x-ouro-input-assets",
    {"structure": {"asset_type": "file", "file_extensions": ["cif"], "primary": True}},
)
@ouro_field(
    "x-ouro-output-assets",
    {"relaxed_structure": {"asset_type": "file", "file_extensions": ["cif"]}},
)
async def predict(request: PredictRequest, ...):
    ...
```

### 3. Deploy it

> Serve it with `modal serve` and hit `/openapi.json`. Confirm the Ouro
> extensions are there. Then deploy it.

Running `modal serve` first gives you a temporary URL that reloads on every
save, so the agent can fix problems before anything is permanent.
`modal deploy` gives you the stable URL you'll register.

### 4. Register it on Ouro

> Create an Ouro service from the deployed spec in my team, with Ouro
> authentication. It wraps a third-party model; set the license and link the
> repo and paper.

The agent calls `create_service` with `spec_url` pointing at your
`/openapi.json`, and Ouro creates one route per endpoint. Attribution matters
here. The model isn't yours, so the service should say so: `originality` set to
`third-party`, the model's `github_url` and `paper_url`, and a license
compatible with the original. See
[licensing and attribution](/docs/developers/mcp#licensing-and-attribution)
for the fields.

With **Ouro** authentication, Ouro sends a shared secret with every call it
forwards and your app rejects calls without it, so nobody can skip Ouro to use your deployment
directly. The service settings on Ouro hold the secret; store it in your
deployment as `OURO_SERVICE_SECRET`. The [Modal
guide](/guides/deploying-to-modal#header-based-authentication-for-ouro-integration)
has the validation code.

### 5. Test it on a known answer

> Run the predict route on a structure whose properties are published, like
> silicon. Dry run first. Compare the result to the literature value, and read
> the action logs if anything fails.

Test on something you already know the answer to before anything new. A route
that returns plausible numbers can still be wrong, and a known answer is the
only way to tell. Ouro records each run as an action: `execute_route` returns
its ID, `get_action` shows status and outputs, and `get_action_logs` shows what
your service logged along the way.

### 6. Fix and redeploy

When something's wrong, the agent edits the code, redeploys, and re-syncs:

> Fix the unit conversion, redeploy, and update the service from the new spec.
> Then rerun the silicon test.

`update_service` with the same `spec_url` re-reads the spec. Routes are matched
on method and path, so existing route IDs, history, and links survive the
update.

### 7. Announce it

> Draft a post announcing the service: what the model does, who made it,
> the silicon test run embedded with its action, and a link to the paper.
> Mention the authors if they're on Ouro.

A route embedded with a real run shows readers the service working on a real
input, with the status, timing, and outputs of that run. It's the strongest
announcement you can make. Review the draft and publish it. If you want to
charge for the route, set pricing in the route's settings; see [how to
monetize APIs](/guides/how-to-sell-apis-on-ouro).

## Grow the skill as you go

Every mistake the agent makes on one service, it will make on the next unless
you write it down. When something goes wrong, add a line to the skill:

- "Normalize `localhost` file URLs to the production storage URL before downloading."
- "Models over 2 GB load in `@modal.enter()`, not per request."
- "Run a known-answer structure before any new material class."

A short skill full of hard-won rules beats a long one full of general advice.
Keep `SKILL.md` under 500 lines and move templates into a `reference.md` the
agent reads only when it needs them. If the skill would help others,
[contribute it](https://github.com/ourofoundation/skills).

## Checklist

Before you announce a service, check that:

- `/openapi.json` shows `x-ouro-input-assets` and `x-ouro-output-assets` on every route that uses assets
- Every route has a description that says when to use it
- The response is a small summary, and large results are output assets
- Jobs over five minutes are async and log their progress
- The service uses Ouro authentication, so calls must come through Ouro
- Attribution names the model's authors, repository, and paper
- A known-answer run succeeded, and its result matches

## Next steps

- [Deploying ML models with Modal](/guides/deploying-to-modal): the manual version of this loop, in depth
- [Designing routes for agents](/guides/agent-friendly-routes): responses agents can use well
- [Long-running APIs](/guides/long-running-apis): webhooks and progress logging
- [Chaining routes into a pipeline](/guides/chaining-routes-into-a-pipeline): composing your route with others
