# 415 Unsupported Media Type in ASP.NET Core Web APIs: Causes and Fixes

The `415 Unsupported Media Type` error is one of those errors that stops you cold. Your endpoint looks correct, your request body looks right, and yet ASP.NET Core refuses to process it. The good news: every root cause has a precise fix, and once you understand how ASP.NET Core content negotiation and input formatting works, this error never catches you off guard again. The complete worked examples — including edge cases for multipart uploads, custom formatters, and integration test patterns — are available on [Patreon](https://www.patreon.com/CodingDroplets), with production-ready source code you can adapt immediately.

Understanding the `415 Unsupported Media Type` error in ASP.NET Core requires knowing how the framework maps incoming request bodies to action parameters. ASP.NET Core doesn't blindly accept any request format — it matches the `Content-Type` header against registered input formatters, then uses binding source attributes to decide where to look for the data. When that negotiation fails, you get a `415`. This article walks through every scenario where this can go wrong, and the exact fix for each one.

## What Does 415 Unsupported Media Type Actually Mean?

HTTP 415 is a client error response code defined in [RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.16). It tells the client that the server understands the request structure but cannot process it because the media type of the request payload is not supported. In ASP.NET Core terms, this means the framework's input formatting pipeline found no registered formatter capable of deserializing the incoming body with the declared `Content-Type`.

The key insight: `415` is always about the **request's** `Content-Type` — not the response. If you're seeing this error, the problem is in what the client is sending, not what your API is returning.

## How ASP.NET Core Processes Request Bodies

Before diving into causes, it helps to understand the pipeline. When a request arrives at an ASP.NET Core controller action:

1. The framework checks the `Content-Type` header of the incoming request.
2. It looks for a registered input formatter that can handle that media type.
3. It uses the binding source attribute on the action parameter (`[FromBody]`, `[FromForm]`, `[FromQuery]`, etc.) to determine where to read the data from.
4. If no formatter matches, or the binding source doesn't align with the request format, ASP.NET Core returns `415`.

`[ApiController]` changes step 3 slightly — it infers `[FromBody]` for complex types automatically, which is helpful but can produce surprising behavior when the client sends a non-JSON body.

## Cause 1: Wrong or Missing Content-Type Header

This is by far the most common cause. Your client is sending a request without a `Content-Type` header, or with a type your API doesn't accept — such as `text/plain` when your endpoint expects `application/json`.

ASP.NET Core registers `application/json` as the default input formatter when you call `AddControllers()` or `AddControllerWithViews()`. If the incoming `Content-Type` doesn't match any registered formatter, the framework returns `415` immediately.

**The fix:** Ensure every POST, PUT, or PATCH request that sends a body includes `Content-Type: application/json`. In Postman or similar tools, set the body type to "raw" and the format to "JSON". In programmatic clients, set the `Content-Type` header explicitly before sending.

This is the most common source of `415` during development when using tools like Postman with default settings — the default body type is "Text", not "JSON".

## Cause 2: Using [FromBody] on a Form-Encoded Request

When a client sends `application/x-www-form-urlencoded` or `multipart/form-data`, ASP.NET Core does not route that data through the JSON input formatter. If your action parameter is decorated with `[FromBody]`, the framework will reject the request with `415` because it's looking for a JSON body and the request contains form data.

**The fix:** Use `[FromForm]` for form-encoded or multipart payloads. `[FromForm]` tells the model binder to read from the form collection rather than the body stream. For file uploads, use `IFormFile` in combination with `[FromForm]`.

The `[ApiController]` attribute does **not** infer `[FromForm]` — it infers `[FromBody]` for complex types, so if your client is sending a form but you're not explicitly using `[FromForm]`, you'll hit `415`.

## Cause 3: The API Doesn't Consume the Required Media Type

If you've decorated your action with `[Consumes("application/xml")]` or a custom media type, the framework enforces it strictly. Any request with a different `Content-Type` — even `application/json` — will be rejected with `415`.

This is intentional behavior, but it catches developers off guard when they forget that `[Consumes]` narrows what the endpoint accepts, not broadens it.

**The fix:** Either remove the `[Consumes]` constraint to accept the default formatter types, or expand it to include all the types the endpoint should handle: `[Consumes("application/json", "application/xml")]`.

If you need XML support across your entire API, add it globally when registering controllers:

```csharp
builder.Services.AddControllers()
    .AddXmlSerializerFormatters();
```

This registers `application/xml` as a supported input and output formatter. Without this, even if you send `Content-Type: application/xml`, the framework has no formatter to process it and returns `415`.

## Cause 4: Sending a Body on a GET Request

Sending a request body with a `GET` verb is technically permitted by HTTP but is widely unsupported in practice. ASP.NET Core's model binding does not read `[FromBody]` parameters for `GET` requests by default. If you've accidentally added a complex type parameter to a GET action without a binding source attribute, ASP.NET Core will infer `[FromQuery]` under `[ApiController]`, not `[FromBody]` — and if the client sends a JSON body with `Content-Type: application/json`, the body is ignored and the response may still return `415` depending on the pipeline configuration.

**The fix:** Do not send request bodies with GET requests. If you need to pass complex filter parameters to a GET endpoint, model them as query string parameters with `[FromQuery]`. If the filtering requirements are genuinely complex, consider a POST endpoint explicitly designed for search (a pattern called POST-for-search or "search as a resource").

## Cause 5: Content Negotiation Failure with Custom Media Types

If your API needs to accept a non-standard media type — for example, a webhook payload from a third-party service that sends `application/vnd.github.event+json` or `application/csp-report` — ASP.NET Core will reject it with `415` unless you explicitly register a formatter that handles it.

**The fix:** Extend the default `SystemTextJsonInputFormatter` to accept the additional media type:

```csharp
builder.Services.AddControllers(options =>
{
    var jsonFormatter = options.InputFormatters
        .OfType<SystemTextJsonInputFormatter>()
        .FirstOrDefault();

    jsonFormatter?.SupportedMediaTypes.Add("application/vnd.github.event+json");
});
```

This tells the existing JSON formatter to also accept requests with that content type, without adding a new formatter.

## Cause 6: Conflict Between [ApiController] and Explicit Binding

`[ApiController]` is opinionated. It automatically infers `[FromBody]` for complex object parameters, which is what you want in most cases. However, if you have an action that accepts a `string` parameter without any binding attribute, `[ApiController]` infers `[FromQuery]` — not `[FromBody]`. If the client sends a JSON string body with `Content-Type: application/json`, the parameter will be empty and the body will fail to bind, sometimes manifesting as a `415`.

**The fix:** Always be explicit about binding sources. Use `[FromBody]` when you expect a JSON body, `[FromQuery]` for query string values, and `[FromRoute]` for route parameters. Rely on `[ApiController]` inference only for complex types where the intent is obvious.

## Cause 7: MaxRequestBodySize Exceeded on File Uploads

On file upload endpoints, exceeding the configured maximum request body size causes ASP.NET Core (specifically the Kestrel or IIS integration layer) to reject the request before it reaches the formatter pipeline. In some configurations this manifests as a `415` rather than the more expected `413 Payload Too Large`, because the rejection happens before content type negotiation completes.

**The fix:** For file upload endpoints that need to accept large payloads, configure the body size limit explicitly. For Kestrel, this is done via `KestrelServerOptions`. For IIS, the `web.config` `maxAllowedContentLength` setting applies. For individual actions, you can use the `[RequestSizeLimit]` and `[DisableRequestSizeLimit]` attributes.

For a deeper look at related request lifecycle issues, see the [OperationCanceledException in ASP.NET Core: Causes and Fixes](https://codingdroplets.com/operationcanceledexception-aspnet-core-causes-and-fixes) article, which covers how request pipeline interruptions propagate.

## How to Diagnose a 415 Error Quickly

When you encounter `415` in an environment where you can't immediately inspect the request, use ASP.NET Core's built-in request logging to capture the incoming `Content-Type` header. With Serilog and `UseSerilogRequestLogging()`, every request logs its method, path, status code, and duration by default.

If you need more granularity, add a short-circuit middleware early in the pipeline that logs the `Content-Type` header before routing occurs. This lets you see exactly what the client is sending regardless of whether the request ever reaches your controller.

For local debugging, always check the response body alongside the status code — ASP.NET Core's Problem Details response for `415` includes the `type`, `title`, and `status` fields defined in [RFC 7807](https://tools.ietf.org/html/rfc7807), which often makes the cause obvious without further investigation.

For a broader perspective on how content negotiation fits into your API design, the [Content Negotiation in ASP.NET Core: JSON vs XML vs MessagePack vs Custom Formatters](https://codingdroplets.com/aspnet-core-content-negotiation-enterprise-decision-guide) guide covers the full decision space.

## Preventing 415 in Production

A few practices eliminate `415` errors before they reach production:

- **Document accepted media types explicitly.** Use `[Consumes]` and `[Produces]` attributes alongside OpenAPI documentation so that client teams know exactly what format each endpoint expects.
- **Write integration tests that send wrong content types.** A test that sends `Content-Type: text/plain` to a POST endpoint and asserts a `415` response protects against formatter configuration regressions.
- **Validate client-generated requests in CI.** If your API has mobile or JavaScript clients, run integration tests against the actual client code in your CI pipeline to catch content-type mismatches before they ship.
- **Return Problem Details on 415.** Enable `AddProblemDetails()` in your service registration so that `415` responses include a structured error body, making client-side debugging faster.

> ☕ Prefer a one-time tip? [Buy us a coffee](https://buymeacoffee.com/codingdroplets) — every bit helps keep the content coming!

## FAQ

**Why do I get 415 in production but not locally?**

The most common reason is a difference in how your local HTTP client (Postman, Swagger UI) sets headers versus how your production client does. Postman's Swagger import might default to `application/json`, while your production JavaScript fetch or HttpClient call doesn't set `Content-Type` explicitly. Always verify the exact headers being sent in production using a request logging middleware or an APM tool.

**Does [ApiController] automatically fix Content-Type issues?**

No. `[ApiController]` simplifies model binding inference and enables automatic 400 responses for validation failures, but it does not modify how input formatters are registered or how `Content-Type` headers are matched. You still need to ensure the registered formatters match what the client sends.

**Can I make my ASP.NET Core API accept any Content-Type without error?**

Yes, but it's not recommended. You can add a wildcard or custom formatter that accepts any media type, but this removes an important safeguard. A better approach is to explicitly register all the media types your API is designed to handle and document them, rather than silently accepting arbitrary formats.

**What is the difference between 415 and 400 Bad Request in ASP.NET Core?**

`415` means the server understands the request structure but cannot process the body because the media type is not supported — the format is wrong. `400` means the request is syntactically or semantically invalid — the format may be correct (JSON), but the content violates a validation rule or is malformed. With `[ApiController]`, model validation failures return `400` automatically via the validation filter, not `415`.

**How do I support both JSON and XML in the same ASP.NET Core API?**

Register XML formatters when you configure `AddControllers()` using `.AddXmlSerializerFormatters()` or `.AddXmlDataContractSerializerFormatters()`. Then either add `[Consumes("application/json", "application/xml")]` to individual actions or allow the default content negotiation to handle it. Clients that send `Content-Type: application/json` will use the JSON formatter; clients that send `Content-Type: application/xml` will use the XML formatter.

**Will [Consumes] on a base controller affect all derived controllers?**

Yes. `[Consumes]` applied to a base controller class is inherited by all derived controllers. This is a common source of unexpected `415` errors when a new endpoint is added to a derived controller and the developer doesn't realize the base class is restricting accepted content types.

**Why does my webhook handler return 415 for valid payloads?**

Third-party webhook providers (GitHub, Stripe, etc.) often send payloads with non-standard `Content-Type` values like `application/vnd.github.event+json` or `application/x-www-form-urlencoded`. Your API's default JSON formatter only accepts `application/json`. Register the additional media type as described in Cause 5, or read the body directly as a stream and deserialize manually to bypass the formatter pipeline entirely.
