API Response Patterns

Comprehensive guide to implementing standard REST API response patterns using EasyMessages in ASP.NET Core.


Table of Contents

  1. Standard REST API Patterns
  2. CRUD Operations
  3. Validation Patterns
  4. Error Handling
  5. Authentication & Authorization
  6. Pagination & Filtering
  7. Best Practices

Standard REST API Patterns

ApiResponse Format

All EasyMessages API responses follow this standardized format via .ToJsonObject():

{
  "success": true,
  "code": "CRUD_001",
  "type": "success",
  "title": "User Created",
  "description": "User has been created successfully.",
  "data": { ... },
  "timestamp": "2026-01-09T14:30:00.000Z",
  "correlationId": "0HMVQK8F3J8QK:00000001",
  "metadata": { ... }
}

Fields:

  • success - Boolean indicating success/failure
  • code - Unique message code (e.g., “CRUD_001”)
  • type - Message type (“success”, “error”, “warning”, “info”)
  • title - Short summary
  • description - Detailed explanation
  • data - Response payload (entity, list, etc.)
  • timestamp - When the message was created (optional)
  • correlationId - Request tracking ID (optional)
  • metadata - Additional context (optional)

Core Pattern

The fundamental pattern for all API responses:

var msg = Msg.Xxx.Method();
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());

For Minimal APIs:

var msg = Msg.Xxx.Method();
return Results.Json(msg.ToJsonObject(), statusCode: msg.HttpStatusCode ?? 200);

CRUD Operations

Create (POST)

Pattern: Return created entity with appropriate status code

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    // Validate
    if (!ModelState.IsValid)
    {
        var err = Msg.Validation.Failed()
            .WithData(new { errors = ModelState });
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422 Unprocessable Entity
    }

    // Create entity
    var user = _userService.Create(dto);

    // Return created response
    var created = Msg.Crud.Created("User")
        .WithData(user)
        .Log(_logger);
    return StatusCode(created.HttpStatusCode ?? 200, created.ToJsonObject()); // 200 OK
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_001",
  "type": "success",
  "title": "User Created",
  "description": "User has been created successfully.",
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "createdAt": "2026-01-09T14:30:00.000Z"
  }
}

Read - Single (GET by ID)

Pattern: Return entity or 404 Not Found

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var user = _userService.FindById(id);

    if (user == null)
    {
        var notFound = Msg.Crud.NotFound("User")
            .WithMetadata("userId", id)
            .Log(_logger);
        return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject()); // 404 Not Found
    }

    var ok = Msg.Crud.Retrieved("User").WithData(user);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject()); // 200 OK
}

Success Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_003",
  "type": "success",
  "title": "User Retrieved",
  "description": "User has been retrieved successfully.",
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  }
}

Not Found Response:

HTTP/1.1 404 Not Found
Content-Type: application/json

{
  "success": false,
  "code": "CRUD_004",
  "type": "error",
  "title": "User Not Found",
  "description": "The requested User could not be found.",
  "metadata": {
    "userId": 999
  }
}

Read - Collection (GET all)

Pattern: Return list of entities

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpGet]
public IActionResult GetAll()
{
    var users = _userService.GetAll();

    var ok = Msg.Crud.Retrieved("Users")
        .WithData(users)
        .WithMetadata("count", users.Count);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject()); // 200 OK
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_003",
  "type": "success",
  "title": "Users Retrieved",
  "description": "Users has been retrieved successfully.",
  "data": [
    { "id": 1, "name": "John Doe" },
    { "id": 2, "name": "Jane Smith" }
  ],
  "metadata": {
    "count": 2
  }
}

Update (PUT)

Pattern: Return updated entity or 404 if not found

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] UpdateUserDto dto)
{
    // Validate
    if (!ModelState.IsValid)
    {
        var err = Msg.Validation.Failed()
            .WithData(new { errors = ModelState });
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422
    }

    // Check exists
    if (!_userService.Exists(id))
    {
        var notFound = Msg.Crud.NotFound("User").WithMetadata("userId", id);
        return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject()); // 404
    }

    // Update
    var user = _userService.Update(id, dto);

    var updated = Msg.Crud.Updated("User")
        .WithData(user)
        .Log(_logger);
    return StatusCode(updated.HttpStatusCode ?? 200, updated.ToJsonObject()); // 200 OK
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_002",
  "type": "success",
  "title": "User Updated",
  "description": "User has been updated successfully.",
  "data": {
    "id": 123,
    "name": "John Doe Updated",
    "email": "john.updated@example.com",
    "updatedAt": "2026-01-09T14:35:00.000Z"
  }
}

Delete (DELETE)

Pattern: Return 200 OK with confirmation message

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
    if (!_userService.Exists(id))
    {
        var notFound = Msg.Crud.NotFound("User").WithMetadata("userId", id);
        return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject()); // 404
    }

    _userService.Delete(id);

    var deleted = Msg.Crud.Deleted("User")
        .WithData(new { id, deletedAt = DateTime.UtcNow })
        .Log(_logger);
    return StatusCode(deleted.HttpStatusCode ?? 200, deleted.ToJsonObject()); // 200 OK
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_005",
  "type": "success",
  "title": "User Deleted",
  "description": "User has been deleted successfully.",
  "data": {
    "id": 123,
    "deletedAt": "2026-01-09T14:40:00.000Z"
  }
}

Validation Patterns

Model Validation

Pattern: Return 422 Unprocessable Entity with validation errors

Controller:

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    if (!ModelState.IsValid)
    {
        var errors = ModelState
            .Where(e => e.Value.Errors.Count > 0)
            .ToDictionary(
                e => e.Key,
                e => e.Value.Errors.Select(x => x.ErrorMessage).ToArray()
            );

        var err = Msg.Validation.Failed()
            .WithData(new { errors });
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422 Unprocessable Entity
    }

    // ... create user
}

Response:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "success": false,
  "code": "VAL_001",
  "type": "error",
  "title": "Validation Failed",
  "description": "One or more validation errors occurred.",
  "data": {
    "errors": {
      "Email": ["Invalid email format"],
      "Password": ["Password must be at least 8 characters"]
    }
  }
}

FluentValidation Integration

Validator:

using FluentValidation;

public class CreateUserDtoValidator : AbstractValidator<CreateUserDto>
{
    public CreateUserDtoValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty()
            .EmailAddress();

        RuleFor(x => x.Password)
            .NotEmpty()
            .MinimumLength(8);
    }
}

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    var validationResult = _validator.Validate(dto);

    if (!validationResult.IsValid)
    {
        var errors = validationResult.Errors
            .GroupBy(e => e.PropertyName)
            .ToDictionary(
                g => g.Key,
                g => g.Select(e => e.ErrorMessage).ToArray()
            );

        var err = Msg.Validation.Failed().WithData(new { errors });
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422
    }

    // ... create user
}

Business Rule Validation

Pattern: Validate business rules and return appropriate error

Controller:

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    // Check if email already exists
    if (_userService.EmailExists(dto.Email))
    {
        var err = Msg.Validation.Duplicate("Email")
            .WithMetadata("email", dto.Email);
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422
    }

    // Check if username is reserved
    if (_userService.IsReservedUsername(dto.Username))
    {
        var err = Msg.Validation.InvalidFormat("Username")
            .WithHint("This username is reserved. Please choose another.");
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422
    }

    // ... create user
}

Response:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "success": false,
  "code": "VAL_014",
  "type": "error",
  "title": "Duplicate Value",
  "description": "The email 'john@example.com' already exists.",
  "metadata": {
    "email": "john@example.com"
  }
}

Error Handling

Global Exception Handling

Middleware:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using RecurPixel.EasyMessages;

public class GlobalExceptionHandler
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionHandler> _logger;

    public GlobalExceptionHandler(RequestDelegate next, ILogger<GlobalExceptionHandler> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (NotFoundException ex)
        {
            await HandleExceptionAsync(context, Msg.Crud.NotFound(ex.ResourceName));
        }
        catch (ValidationException ex)
        {
            await HandleExceptionAsync(context, Msg.Validation.Failed()
                .WithData(new { errors = ex.Errors }));
        }
        catch (UnauthorizedAccessException)
        {
            await HandleExceptionAsync(context, Msg.Auth.Unauthorized());
        }
        catch (DbUpdateException ex)
        {
            await HandleExceptionAsync(context, Msg.Database.TransactionFailed()
                .WithMetadata("error", ex.Message));
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(context, Msg.System.Error()
                .WithMetadata("error", ex.Message));
        }
    }

    private async Task HandleExceptionAsync(HttpContext context, Message message)
    {
        _logger.LogError("Unhandled exception for request {Path}", context.Request.Path);

        var enriched = message.WithCorrelationId(context.TraceIdentifier);
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = enriched.HttpStatusCode ?? 500;

        await context.Response.WriteAsJsonAsync(enriched.ToJsonObject());
    }
}

// Register in Program.cs
app.UseMiddleware<GlobalExceptionHandler>();

Database Errors

Pattern: Handle database-specific errors

Controller:

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RecurPixel.EasyMessages;

[HttpPost]
public IActionResult Create([FromBody] CreateUserDto dto)
{
    try
    {
        var user = _userService.Create(dto);
        var created = Msg.Crud.Created("User").WithData(user);
        return StatusCode(created.HttpStatusCode ?? 200, created.ToJsonObject());
    }
    catch (DbUpdateException ex) when (ex.InnerException is SqlException sqlEx && sqlEx.Number == 2627)
    {
        // Unique constraint violation
        var err = Msg.Validation.Duplicate("User")
            .WithMetadata("error", "A user with this email already exists");
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject()); // 422
    }
    catch (DbUpdateException ex)
    {
        var err = Msg.Database.TransactionFailed()
            .WithMetadata("error", ex.Message)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 500, err.ToJsonObject()); // 500
    }
}

Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json

{
  "success": false,
  "code": "DB_004",
  "type": "error",
  "title": "Transaction Failed",
  "description": "The database transaction failed and has been rolled back.",
  "metadata": {
    "error": "Connection timeout occurred"
  }
}

External Service Errors

Pattern: Handle third-party service failures

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpPost("send-email")]
public async Task<IActionResult> SendEmail([FromBody] EmailDto dto)
{
    try
    {
        await _emailService.SendAsync(dto);
        var ok = Msg.System.OperationCompleted().WithData(new { sent = true });
        return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject()); // 200 OK
    }
    catch (HttpRequestException ex)
    {
        var err = Msg.System.Unavailable()
            .WithMetadata("service", "EmailService")
            .WithMetadata("error", ex.Message)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 503, err.ToJsonObject()); // 503 Service Unavailable
    }
    catch (TimeoutException)
    {
        var err = Msg.Network.Timeout()
            .WithMetadata("service", "EmailService")
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 408, err.ToJsonObject()); // 408 Timeout
    }
}

Authentication & Authorization

Login Success

Pattern: Return user info with auth token

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpPost("login")]
public IActionResult Login([FromBody] LoginDto dto)
{
    var user = _authService.Authenticate(dto.Email, dto.Password);

    if (user == null)
    {
        var failed = Msg.Auth.LoginFailed()
            .WithMetadata("email", dto.Email)
            .Log(_logger);
        return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject()); // 401 Unauthorized
    }

    var token = _tokenService.GenerateToken(user);

    var ok = Msg.Auth.LoginSuccessful()
        .WithData(new
        {
            user = new { user.Id, user.Name, user.Email },
            token,
            expiresAt = DateTime.UtcNow.AddHours(24)
        })
        .Log(_logger);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject()); // 200 OK
}

Success Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "AUTH_003",
  "type": "success",
  "title": "Login Successful",
  "description": "Welcome back, john!",
  "data": {
    "user": {
      "id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    },
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresAt": "2026-01-10T14:30:00.000Z"
  }
}

Failure Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "success": false,
  "code": "AUTH_001",
  "type": "error",
  "title": "Authentication Failed",
  "description": "Invalid username or password.",
  "metadata": {
    "email": "john@example.com"
  }
}

Unauthorized Access

Pattern: Return 403 Forbidden for insufficient permissions

Controller:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public IActionResult Delete(int id)
{
    // Check if user has permission
    if (!User.IsInRole("Admin"))
    {
        var err = Msg.Auth.Unauthorized()
            .WithMetadata("requiredRole", "Admin")
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 403, err.ToJsonObject()); // 403 Forbidden
    }

    _userService.Delete(id);

    var deleted = Msg.Crud.Deleted("User");
    return StatusCode(deleted.HttpStatusCode ?? 200, deleted.ToJsonObject());
}

Response:

HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "success": false,
  "code": "AUTH_002",
  "type": "error",
  "title": "Unauthorized Access",
  "description": "You do not have permission to perform this action.",
  "metadata": {
    "requiredRole": "Admin"
  }
}

Expired Token

Pattern: Return 401 with token expired message

In JWT middleware/filter:

if (tokenExpired)
{
    var expired = Msg.Auth.SessionExpired();
    context.Response.StatusCode = expired.HttpStatusCode ?? 401;
    await context.Response.WriteAsJsonAsync(expired.ToJsonObject());
    return;
}

Response:

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "success": false,
  "code": "AUTH_004",
  "type": "error",
  "title": "Session Expired",
  "description": "Your session has expired."
}

Pagination & Filtering

Paginated Response

Pattern: Return page of results with pagination metadata

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpGet]
public IActionResult GetAll([FromQuery] int page = 1, [FromQuery] int pageSize = 10)
{
    var result = _userService.GetPaged(page, pageSize);

    var ok = Msg.Crud.Retrieved("Users")
        .WithData(result.Items)
        .WithMetadata("pagination", new
        {
            page,
            pageSize,
            totalCount = result.TotalCount,
            totalPages = result.TotalPages,
            hasNext = result.HasNext,
            hasPrevious = result.HasPrevious
        });
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "CRUD_003",
  "type": "success",
  "title": "Users Retrieved",
  "description": "Users has been retrieved successfully.",
  "data": [
    { "id": 1, "name": "User 1" },
    { "id": 2, "name": "User 2" },
    { "id": 3, "name": "User 3" }
  ],
  "metadata": {
    "pagination": {
      "page": 1,
      "pageSize": 10,
      "totalCount": 25,
      "totalPages": 3,
      "hasNext": true,
      "hasPrevious": false
    }
  }
}

Filtered/Searched Response

Pattern: Return filtered results with search criteria

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpGet("search")]
public IActionResult Search([FromQuery] string query, [FromQuery] string role = null)
{
    var users = _userService.Search(query, role);

    var ok = Msg.Search.Completed(users.Count, query)
        .WithData(users)
        .WithMetadata("role", role);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "SEARCH_002",
  "type": "success",
  "title": "Search Completed",
  "description": "Found 2 result(s) for 'john'.",
  "data": [
    { "id": 5, "name": "John Doe", "role": "Admin" },
    { "id": 12, "name": "John Smith", "role": "User" }
  ],
  "metadata": {
    "query": "john",
    "role": "Admin",
    "count": 2
  }
}

No Results Found

Pattern: Return empty array with informative message

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

[HttpGet("search")]
public IActionResult Search([FromQuery] string query)
{
    var users = _userService.Search(query);

    if (!users.Any())
    {
        var empty = Msg.Search.NoResults(query)
            .WithData(new List<object>());
        return StatusCode(empty.HttpStatusCode ?? 200, empty.ToJsonObject()); // 200 OK with empty array
    }

    var ok = Msg.Search.Completed(users.Count, query).WithData(users);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "success": true,
  "code": "SEARCH_001",
  "type": "info",
  "title": "No Results Found",
  "description": "No results found for 'xyz123'.",
  "data": [],
  "metadata": {
    "query": "xyz123"
  }
}

Best Practices

[✓] DO:

  1. Use StatusCode(msg.HttpStatusCode ?? N, msg.ToJsonObject())
    // Good - Appropriate status codes
    var created = Msg.Crud.Created("User").WithData(user);
    return StatusCode(created.HttpStatusCode ?? 200, created.ToJsonObject());
    
    var notFound = Msg.Crud.NotFound("User");
    return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
    
    var err = Msg.Validation.Failed().WithData(errors);
    return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject());
    
  2. Include relevant data in responses
    // Good - Return created entity
    var created = Msg.Crud.Created("User").WithData(user);
    return StatusCode(created.HttpStatusCode ?? 200, created.ToJsonObject());
    
  3. Add metadata for debugging
    // Good - Helpful metadata
    var notFound = Msg.Crud.NotFound("User")
        .WithMetadata("userId", id)
        .WithMetadata("requestedBy", User.Identity.Name);
    return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
    
  4. Log errors and warnings
    // Good - Log before returning
    var failed = Msg.Auth.LoginFailed()
        .WithMetadata("email", dto.Email)
        .Log(_logger);
    return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject());
    
  5. Assign the message to a variable before returning
    // Good - Readable and safe
    var msg = Msg.Crud.Created("User").WithData(user).Log(_logger);
    return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
    

[ ] DON’T:

  1. Don’t return inconsistent formats
    // Bad - Custom format
    return Ok(new { message = "User created" });
    
    // Good - Standard format
    var msg = Msg.Crud.Created("User").WithData(user);
    return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
    
  2. Don’t expose sensitive data
    // Bad - Includes passwordHash
    var msg = Msg.Crud.Retrieved("User").WithData(user);
    
    // Good - Safe DTO without sensitive fields
    var msg = Msg.Crud.Retrieved("User").WithData(new UserDto(user));
    
  3. Don’t ignore validation
    // Bad - No validation
    var user = _userService.Create(dto);
    
    // Good - Validate first
    if (!ModelState.IsValid)
    {
        var err = Msg.Validation.Failed().WithData(new { errors = ModelState });
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject());
    }
    
  4. Don’t swallow exceptions silently
    // Bad - Silent failure
    try { _userService.Delete(id); }
    catch { } // Error hidden!
    
    // Good - Log and return error
    try
    {
        _userService.Delete(id);
    }
    catch (Exception ex)
    {
        var err = Msg.System.Error()
            .WithMetadata("error", ex.Message)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 500, err.ToJsonObject());
    }
    
  5. Don’t hardcode status codes as magic numbers
    // Bad - Magic number
    return StatusCode(200, msg.ToJsonObject());
    
    // Good - Use message's status code
    return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
    

Next Steps


Questions? Ask in Discussions