GPT-6.1 Terra for C#: Fast Vision Agents at Lower Cost

GPT-6.1-Terra-for-CSharp-Fast-Vision-Agents-at-Lower-Cost

An agent that only reads text is limited. Much of real work lives in screenshots, scanned forms, product photos, and the screens of apps that have no API. Those jobs need a model that can see the picture and act on it, and the bill has to stay reasonable when the agent runs hundreds of times a day.

GPT-6.1 Terra is now generally available in Microsoft Foundry. Microsoft positions it for high-volume visual and screen-based work, and it says Terra is quick and efficient for day-to-day professional tasks.

Sources for the facts in this post: the Foundry announcement and the Responses API documentation. The pricing is Microsoft’s published table, and I haven’t checked it against your region or contract.

The full sample is on GitHub: GPT-6-1-Terra-CSharp-Sample.

What Terra is good at

Microsoft lists four core strengths. For a C# developer, each one maps to a concrete kind of work.

  • Stronger vision. Better visual reasoning picks out the details in product photos, technical drawings, floor plans, and application screens.
  • Faster, more precise computer use. The model pairs what it sees with quick, accurate actions in browsers, desktop software, and apps.
  • Clear writing. It summarizes what it finds in plain language and drafts emails and proposals.
  • Large context. A 1.05M-token context window, with up to 922K input tokens and 128K output tokens. It takes text and image input.

Microsoft’s own examples are a good shortlist: reviewing campaign assets for cropping and text placement, matching supplier product images to catalog entries, finding a moment in a video, and completing forms. Each one starts with a picture and ends in software.

One note on video: the model takes text and images, not video files. To find a moment in a video, your app pulls frames at an interval, sends them as images, and keeps track of each frame’s timestamp itself.

Where it fits, and where it doesn’t

Terra is not the model for the hardest reasoning. Microsoft says it complements GPT-6 Astra: Astra plans open-ended, multi-step work, and Terra handles the high-volume visual and on-screen tasks inside that plan, at a lower price point.

Use Terra for Keep another model for
Reading screenshots, scans, and product photos Open-ended planning across many steps
Form filling and routine email or proposal drafts Long reasoning on hard technical problems
Retail catalog checks and listing updates Decisions that need a strong, explained rationale
Finding a moment in video frames and acting on it A fast, typed answer with no generated text (use the Decisions API)

For the last row, Microsoft suggests pairing the two. Terra handles the visual work, and the Decisions API makes the quick call on what happens next. That is the same split I described for the Decisions API.

The developer controls

Three controls matter for a .NET app.

  • Reasoning effort. You can set it, including none for latency-sensitive calls. The sample uses low.
  • Verbosity. It matches the length of the response to the task. The sample uses low.
  • Interim updates. When your orchestration layer supports it, Terra can send messages to the user while it keeps working on a long task.

The sample sends reasoning.effort and text.verbosity in the request body. Check the exact names and allowed values in the Responses API reference before you rely on them, because the parts I checked did not show the full parameter list.

The API is the Responses API

Terra works with the Foundry APIs you already use, through the Responses API, with no new SDK or schema changes. That matters for C#: you do not need a Terra-specific package. A plain HttpClient call is enough, which is what the sample does.

You can use the OpenAI .NET SDK instead. Microsoft’s Responses API docs show it with the OpenAI and Azure.Identity packages and a ResponsesClient pointed at your resource’s /openai/v1 endpoint, and it gives you typed requests and responses instead of hand-built JSON. Its Responses types are marked experimental, so the docs’ examples suppress the OPENAI001 warning. I kept HttpClient here because it shows exactly what goes over the wire, which helps when you’re debugging a tool loop.

The request goes to:

The model field takes your deployment name, not the model name.

Authentication has two options, and the sample supports both:

  • API key: sent in the api-key header.
  • Microsoft Entra ID: a bearer token for the scope https://ai.azure.com/.default, from DefaultAzureCredential. Locally, az login is enough.

If you set an API key, the sample uses it. If you don’t, the sample signs in with Entra ID. Use Entra ID for anything that runs in production. When the app runs in Azure, DefaultAzureCredential picks up its managed identity, so there is no secret to store at all.

Use the Responses API for tool calling too. Microsoft’s reasoning models guide recommends it whenever you combine reasoning with function tools, because it returns reasoning items you can pass back between turns. It also documents a trap on the GPT-5.6 models: a Chat Completions request that sends tools fails unless reasoning_effort is none, because those models default to medium. The guide doesn’t list that limit for Terra, but if you’re moving an existing Chat Completions tool loop to a newer reasoning model, test the tool calls before you switch.

What it costs

Microsoft’s published prices in USD per million tokens:

Deployment Context Input Cached input Output
Global Standard Short $1.00 $0.05 $5.00
Global Standard Long $2.00 $0.10 $7.50
Data Zone Standard (US) Short $1.10 $0.055 $5.50
Data Zone Standard (EU) Short $1.20 $0.06 $6.00
Data Zone Standard (APAC) Short $1.20 $0.06 $6.00

The announcement lists Data Zone Standard for US, EU and APAC, so you can keep data in a region without a separate model. On short context, output tokens cost about five times input tokens, so a short answer is cheap and a long generated report is not. Reasoning tokens count as output, which is one more reason to set reasoning effort on purpose.

Watch the line between short and long context. On OpenAI’s own API, a request with more than 272K input tokens is billed at the long-context rate for the whole request, not just the tokens past the line. One token over reprices the entire call. I haven’t found that threshold in Microsoft’s pricing for Foundry, so check your bill before you assume it works the same way. If an agent keeps adding screenshots to a long conversation, trim the history before it crosses the line.

Calling Terra from C#

The sample is one console app with two examples:

  1. Read a screenshot. Terra looks at a PNG of a web form and says what the user is trying to do, what blocks them, and which control to use next.
  2. Review a PDF invoice. Terra reads the invoice, calls a C# function to check the supplier, and returns a structured result that deserializes into a C# record.

Both print token usage and elapsed time. The sample files, expense-form.png and invoice.pdf, are in the GitHub repo. Contoso, Fabrikam and everything on them are made up.

To skip the setup below, clone it and go straight to the settings:

To build it from scratch instead, create the project on .NET 10 and add the packages:

Store the settings with user secrets. The endpoint is the base URL with no path. The API key line is optional. Leave it out to use Entra ID:

For Entra ID, run az login and make sure your account has access to call the model on the resource. Check the role name in the Foundry access settings for your resource. Then check what the app will read:

That command prints your API key in plain text, so don’t paste its output into an issue, a log, or a screenshot.

The project file is GPT-6-1-Terra-CSharp-Sample.csproj. Keep the UserSecretsId that dotnet user-secrets init wrote into your own project file. If you copy someone else’s, your secrets won’t be found:

Then Program.cs:

Example 1: reading a screenshot

The first request sends a question and the PNG together in one user message. The image goes in as a base64 data URL, and the data URL has to carry the real type of the file. MimeTypeFor maps the extension to image/png, image/jpeg, image/webp, or image/gif, which are the formats the Responses API accepts.

The detail setting decides how closely the model looks:

detail What it does Use it for
low A lower-resolution copy of the image. Fewest tokens, fastest. Rough checks, such as “is this a receipt or a product photo?”
high A higher-resolution copy. More tokens, slower. Small text, such as the red field hints on this form
auto The default. The model picks. When you don’t know what the images will look like

The sample uses high because the answer depends on two small error messages under the fields. Run it once with low and compare the answer and the input tokens.

This is the reading half of computer use. The model doesn’t click anything. It tells your code what it sees and what to do next, and your code, or a browser automation tool such as Playwright, does the click. Then you send the next screenshot.

Example 2: an invoice agent

The second request does three things the first one doesn’t.

It sends a PDF. The invoice goes in as an input_file item with a base64 data:application/pdf URL. Microsoft’s docs say models that take both text and images can read PDFs, and that the model sees both the extracted text and an image of each page. That helps with tables, stamps and scanned pages, but it also means a PDF costs more tokens than its text alone. You can also send a file ID from the Files API or a file_url instead of base64. Each request is limited to 50 MB of files.

It calls a C# function. The request declares one tool, check_supplier, with a JSON schema for its argument. Terra reads the supplier name off the invoice and asks for the call. The app finds the function_call item in the output, runs CheckSupplierAsync, and sends the result back as a function_call_output with the same call_id. The loop stops when Terra returns no more calls, with a limit of five rounds so a confused model can’t loop forever.

The follow-up request uses previous_response_id instead of sending the whole conversation again. The service keeps the earlier turn, including the reasoning items, which the reasoning guide says you should carry across tool calls. This needs stored responses, which is the default. Microsoft keeps stored responses for 30 days unless you delete them.

It returns structured output. text.format holds a JSON schema with strict set to true, so the final answer has to match it: supplier, invoice number, date, PO number, currency, total, whether it needs approval, and a list of issues. Strict mode needs every property in required and additionalProperties set to false. The app deserializes the answer into the InvoiceReview record with System.Text.Json, using the snake_case naming policy so supplier_name maps to SupplierName.

The approval rule lives in the instructions: anything over 5,000, or from a supplier that isn’t on file and active, needs approval. In a real system, keep that rule in your own code as well, and treat the model’s requires_approval as a second opinion, not the decision.

CheckSupplierAsync is a dictionary standing in for your ERP. Replace it with a real lookup, and keep it read-only. A tool that only looks things up is a safe first tool. A tool that pays an invoice is not.

Run it

Run it from the project folder, because the app loads the sample files from there. Pass 1 for the screenshot, 2 for the invoice agent, or all (the default) for both:

Anything else prints the usage and exits with code 1. Each example is its own class, ScreenshotExample and InvoiceAgentExample, and both send requests through one small TerraClient, so you can copy either one into your own app.

Your output will look like this. The numbers depend on your prompt, your deployment, and your region:

Look at the one issue Terra raised. My test invoice has a footer line saying it’s a sample document for a code demo, with fictitious companies, and Terra flagged it even though nothing in the instructions mentioned it. That’s a fair catch: a real invoice that calls itself a sample is exactly what a reviewer should see. It’s also why the issues list goes to a person instead of being dropped. The model will notice things your rules didn’t anticipate, and some of them will matter.

What to measure before you ship

  • Latency. Run the same screen or image through Terra at each reasoning effort. The elapsed value is wall-clock time for one run, which is one sample, not a benchmark. The invoice example makes at least two calls, one before the tool call and one after, so expect it to take longer than the screenshot.
  • Tokens. Compare input, cached, output, and reasoning across runs. Reasoning tokens are billed as output. Try detail at low and high on the screenshot, and note what one PDF page costs you.
  • Accuracy on your data. Microsoft’s examples are not your screens, and my invoice is not your invoices. Test on your own forms and documents, and compare the structured output with what a person would enter, before you trust it.

Computer use needs guardrails

Terra can act on screens, so treat its actions like any automation. Run it with the least permission it needs, keep a human approval step on anything that spends money, changes records, or sends a message, and log every action. The sample’s only tool is a read-only supplier lookup, and the screenshot example only describes what it sees, so neither can change anything. The pattern matters as soon as you give Terra a tool that writes, or connect a real browser or desktop.

Don’t rely on the prompt to keep it in bounds. According to reporting on OpenAI’s GPT-6.1 Terra system card, when Terra ran into an environment restriction such as denied access at maximum reasoning effort, it found another way to finish the task in 29.6% of test scenarios. That is higher than GPT-6 Astra and GPT-6.1 Sol. The same reporting says most of those tests were low risk and had no system-level enforcement, so read the number as a tendency, not a measured failure rate. I haven’t checked the figure against the system card itself. Either way, the lesson is the same: enforce limits in the environment, with a sandbox, scoped credentials, and an allow list, not only in the instructions.

Final thoughts

Terra is the model I would reach for when the input is a picture or a screen and the output is a small action or a clear summary, at volume. For the hard reasoning, keep a stronger model. For the quick typed call, use the Decisions API. Terra sits between them. The sample gives you the two patterns most of that work is built from: read a screen and say what to do next, and turn a document into typed data after checking it against your own system. It works with an API key today and with Entra ID for production.

Resources

Building AI features in C#? I write about practical, no-hype prompt engineering and Azure AI patterns for .NET developers. Check out Prompt Engineering for .NET Developers — free, no Python required. You can also subscribe for more posts.

Taswar Bhatti Avatar

Written by