Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To create an API in PHP that can return JSON, XML, or HTML, keep the application logic in one service layer and let an HTTP controller handle routes, validation, status codes, headers, and serialization. Choose a supported representation explicitly—through a format parameter or the request’s Accept header—and send a matching Content-Type with every response. The examples below show the key pieces and the decisions needed to make them safe and predictable.

What makes a PHP endpoint an API?

An API is an HTTP contract, not just a PHP script that prints data. Clients need to know which routes and methods to use, what inputs are accepted, which response formats are available, what status codes mean, and how errors are shaped. Define those rules before adding serializers.

A useful request flow is:

  1. Match the route and HTTP method.
  2. Authenticate the caller and authorize access to the requested resource or action.
  3. Check the request media type and body size, then parse and validate the input.
  4. Call application or domain logic that returns data independently of HTTP format.
  5. Select an allowed response representation and serialize it.
  6. Set the status and headers, then emit the body.

For example, the same user lookup can return a JSON object to an application, XML to an established integration, or an HTML page to a person. Keep the lookup and permission checks shared; give each representation its own serializer.

Choose how clients select JSON, XML, or HTML

There are two common designs. A format parameter is simple to understand and explicit; /users/42?format=json and /users/42?format=xml select a representation. Allowlist the values and reject unknown formats rather than using user input as a header value. Alternatively, negotiate using Accept, selecting among media types such as application/json, application/xml, and text/html.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If you use Accept, document what happens when the header is absent and how quality values and conflicts are handled. If none of the requested media types is supported, return 406 Not Acceptable. If you support both a format parameter and Accept, specify which takes precedence and test that rule. Add Vary: Accept when a cache may store different representations of the same URL. For sensitive responses, use an appropriate cache policy such as Cache-Control: no-store.

Do not copy an arbitrary Accept value into Content-Type. OWASP’s REST guidance states that the request or response body should match the intended content type in its header. A request with an unsupported body media type commonly receives 415 Unsupported Media Type.

Return JSON safely with PHP

PHP’s json_encode() returns a string containing the JSON representation of a value. Strings supplied to it must be UTF-8. Use JSON_THROW_ON_ERROR so encoding failures are handled deliberately instead of silently producing an unusable response.

<?php
$data = [
    'id' => $user['id'],
    'name' => $user['name'],
];

header('Content-Type: application/json; charset=utf-8');
echo json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);

Choose a stable shape for collections and errors. For example, a collection can use {"data":[...],"meta":{...}}, while an error can use {"error":{"code":"invalid_request","message":"..."}}. Keep the schema consistent across routes, and do not send database exceptions, file paths, or stack traces to clients. Log diagnostic detail on the server instead.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

In a real controller, catch JsonException or other serialization failures at the HTTP boundary, log them with a request or correlation ID, and return a generic server error if a response has not already been sent. Do not let partial output precede a JSON error response.

Build XML with a document API

Use DOMDocument to create XML rather than concatenating markup around values. Text nodes let the XML library handle characters such as ampersands and angle brackets. PHP’s DOM extension uses UTF-8 encoding.

<?php
$doc = new DOMDocument('1.0', 'UTF-8');
$root = $doc->createElement('user');

$id = $doc->createElement('id');
$id->appendChild($doc->createTextNode((string) $user['id']));
$root->appendChild($id);

$name = $doc->createElement('name');
$name->appendChild($doc->createTextNode((string) $user['name']));
$root->appendChild($name);
$doc->appendChild($root);

header('Content-Type: application/xml; charset=utf-8');
echo $doc->saveXML();

For incoming XML, require an accepted XML media type, cap the request size, and validate the resulting fields and business rules. Configure parsing defensively: unsafe external-entity processing can expose local files or cause network requests. Do not assume that well-formed XML is safe or valid for your application.

Render HTML without turning data into code

Use a server-side template or a deliberately escaped view when HTML is a supported representation. HTML text, attributes, URLs, JavaScript, and CSS are different output contexts; escaping for one context does not automatically make a value safe in another. For ordinary text and quoted attribute values, PHP’s htmlspecialchars() with quotes and UTF-8 handling is a common starting point:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
$nameForHtml = htmlspecialchars(
    (string) $user['name'],
    ENT_QUOTES | ENT_SUBSTITUTE,
    'UTF-8'
);

header('Content-Type: text/html; charset=utf-8');
echo '<!doctype html><html lang="en"><body>';
echo '<h1>' . $nameForHtml . '</h1>';
echo '</body></html>';

That example escapes a value for an HTML text context; it is not a universal encoder for JavaScript, CSS, or URLs. Validate URL schemes and use context-specific handling if values appear in those locations.

If browser code fetches JSON and displays a value, insert it as text rather than interpreting it as markup. For example, assign to textContent or append a text node. Do not put untrusted API data into innerHTML: attacker-controlled markup can execute in the page.

Send Content-Type: text/html; charset=utf-8 for HTML and consider X-Content-Type-Options: nosniff. Explicit MIME types reduce the risk of browsers treating a response as a different kind of content.

Parse and validate JSON requests

For a JSON request, first require Content-Type: application/json (allowing parameters such as a charset if your contract permits them). Read the raw body from php://input, decode it, confirm its expected top-level shape, and validate each field before using it. Parsing only proves that the syntax is valid; it does not prove that the request is authorized or that its values make sense.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (stripos($contentType, 'application/json') !== 0) {
    http_response_code(415);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'error' => ['code' => 'unsupported_media_type', 'message' => 'Send application/json.'],
    ], JSON_THROW_ON_ERROR);
    exit;
}

$rawBody = file_get_contents('php://input');
try {
    $input = json_decode($rawBody, false, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
    http_response_code(400);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'error' => ['code' => 'invalid_json', 'message' => 'The request body is not valid JSON.'],
    ], JSON_THROW_ON_ERROR);
    exit;
}

if (!is_object($input)) {
    http_response_code(400);
    // Return the same documented JSON error envelope here.
    exit;
}

// Validate allowed fields, types, lengths, ranges, and business rules before use.

The example demonstrates the parsing boundary, not a complete endpoint. Enforce a body-size limit before accepting input, define whether unknown fields are rejected, and return a documented 400 for malformed syntax or 422 Unprocessable Content (often called a 422-style validation error) for syntactically valid but invalid values. Keep error messages useful to the client without revealing internals.

Use status codes and headers as part of the contract

Every response body needs the media type that describes the representation actually sent. Set the status before writing output, and avoid emitting warnings or whitespace before headers. A practical contract should explain success, client errors, and server errors rather than returning 200 for every outcome.

  • 200 for a successful read or operation with a response body; 201 when a resource is created.
  • 400 for malformed requests, 401 when authentication is required or invalid, and 403 when the authenticated caller lacks permission.
  • 404 when the route or resource is not available to the caller, and 405 when the route does not allow the method. A 405 response should identify allowed methods where applicable.
  • 406 when no requested response representation is supported, 415 for an unsupported request body media type, and 422 for validly parsed input that fails application validation.
  • 429 when rate limits are exceeded and 500 for an unexpected server failure.

Choose exact status and error behavior consistently and document it. Set X-Content-Type-Options: nosniff, a deliberate cache policy, and any other headers required by your deployment. For browser clients, configure CORS only for known origins and make credential handling explicit; CORS is not a substitute for authentication or authorization.

Keep outbound HTTP calls bounded and checked

When PHP calls another API, encode JSON and declare the request media type instead of sending an unlabelled body. PHP’s cURL supports setting the method, body, headers, and response handling:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
$payload = ['name' => $name];
$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 15,
]);

$response = curl_exec($ch);
if ($response === false) {
    $error = curl_error($ch);
    // Log transport details; return a safe application-level error.
}
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$responseType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);

Choose timeouts appropriate to the operation and also cap how much response data your application will accept. Check transport failures, HTTP status, response size, and response media type before decoding an upstream body. Treat non-2xx responses and malformed data as explicit cases, not successful results. Avoid putting credentials or tokens in query strings, where they can leak into logs and other records.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Secure the API at every layer

Serialization does not secure an endpoint. Apply controls to the route, caller, input, database, and deployment:

  • Use HTTPS in production and keep credentials and tokens out of logs.
  • Authenticate callers and authorize every resource and action. Knowing a valid user ID is not permission to access that user’s record.
  • Validate methods, media types, body size, field types, lengths, ranges, and business rules.
  • Use prepared database statements and a database account with only the privileges the API needs.
  • Return generic client-facing errors; log server-side details with a correlation ID that helps connect a response to an operational record.
  • Apply rate limits to expensive or authenticated operations and cap pagination limits.

For XML specifically, harden the parser against external entities and resource abuse. For HTML, escape values for their output context. For negotiated responses, ensure cache keys distinguish representations or disable storage when the content is sensitive.

Compare the representations before supporting all three

Supporting more formats adds serializers, tests, and compatibility decisions. Offer a representation because a client needs it, not simply because the endpoint can emit it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Representation Best fit Design concern
JSON Most programmatic clients and web applications Define stable field names and error envelopes; handle UTF-8 and encoding failures.
XML Existing integrations that require XML structure, schemas, or namespaces Use a document API, define the schema and compatibility rules, and harden input parsing.
HTML A human-facing page served by the same route or application Escape according to context and account for browser caching and rendering behavior.

JSON is often a sensible default for programmatic consumers. XML can be necessary for established integrations, especially when their contract depends on namespaces or a defined document structure. HTML makes sense when a route is also intended to serve a person. Choose one canonical application data model and test each serializer against the contract clients consume.

Test and document the behavior clients depend on

Test each route, method, and supported representation—not only the happy-path JSON response. Include:

  • Successful reads and creations with the intended status and exact media type.
  • Malformed JSON, invalid UTF-8, oversized bodies, wrong top-level shapes, unknown fields, and invalid business values.
  • Authorization checks across users or tenants, including attempts to access another caller’s object.
  • Unsupported methods and media types, unknown formats, unsupported Accept values, and cache behavior for negotiated responses.
  • XML parser attack cases and HTML rendering with hostile strings.
  • Rate-limit responses, upstream timeouts, malformed upstream responses, and non-success upstream statuses.

Document routes and methods, authentication, parameters, request and response schemas, error codes, pagination, rate limits, and supported media types. An API description such as OpenAPI can make the contract easier for clients and tests to use. The OWASP API testing guidance treats CRUD behavior, content types, and API descriptions as core concerns, not optional polish.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.