← Blog
RISE CRMAPIPHP

RISE CRM API 404 Errors: The index.php URL Fix

A RISE CRM API 404 is nearly always a URL problem, not a broken install. The CodeIgniter index.php fix, plus the three silent failures that look identical.

You installed the plugin, activated it, generated a key, pasted the endpoint into Postman and got a 404. The CRM itself is fine - you are logged into it in the next tab. Nothing in the response body mentions authentication, so you assume the endpoint does not exist, and the next hour goes into re-reading the docs for a path you have already typed correctly. A RISE CRM API 404 in this exact shape is almost never a missing endpoint. It is a URL that never reached the application at all.

We build and sell the REST API plugin for RISE CRM, so weigh this accordingly. The routing behaviour underneath it belongs to CodeIgniter and your web server, though, and applies whether the request came from our plugin’s documentation or from code you wrote yourself.

Why a RISE CRM API 404 is usually a routing problem

RISE runs on CodeIgniter, which uses a front controller. Every request the framework handles is meant to arrive at a single PHP file - index.php - which then reads the rest of the path and works out which controller and method should answer. A URL like /api/leads is not a directory on disk. There is no api folder containing a leads file. The path is data that index.php interprets.

The tidy URL only works if something rewrites it. On Apache that is a .htaccess rule; on nginx it is a try_files directive in the server block. When that rewrite is missing, misconfigured or ignored, the web server does exactly what it was asked: it looks for a file at /api/leads, does not find one, and returns its own 404 before PHP is ever invoked.

This is why the answer is so often the ugly version of the URL:

https://crm.example.com/index.php/api/...

instead of

https://crm.example.com/api/...

Because RISE runs on CodeIgniter, some server configurations require /index.php/ to be included in your API endpoint URLs. Our documentation covers it, and it is worth trying before anything else, because it costs ten seconds and eliminates the single most common cause.

Why the web interface works when the API does not

The obvious objection: if rewriting is broken, why does the CRM itself load? Because the application generates its own internal links through the framework’s URL helper, using the base URL held in its configuration. If that configuration already includes the front controller, every link the interface renders carries it, and you never notice because you never type it. An API URL you assembled by hand from documentation inherits nothing. A working browser session is not proof that rewriting works - it is proof that the application knows how to talk to itself.

Prove it in one command

Do not test this in Postman first. Test it where you can see the raw exchange.

curl -i "https://crm.example.com/api/leads"
curl -i "https://crm.example.com/index.php/api/leads"

Compare the two. What matters is not just the status line but the Server and Content-Type headers and the first few lines of the body. Three outcomes, three different problems:

  • An HTML page that looks like your host’s or the server’s default 404. PHP never ran. This is the rewrite problem, and /index.php/ will usually fix it on the spot.
  • A JSON error, or an HTML page that is clearly RISE’s own. The request reached the application. The route is being evaluated and rejected, so now you have a real path, version or HTTP method question.
  • A 401 or 403 rather than a 404. Routing is fine and you have an authentication problem. Skip to the section on keys.

That distinction is the whole diagnosis. Everything else is a consequence of which of the three you got.

Fix the rewrite, or keep index.php on purpose

If /index.php/ works, you have a decision rather than a fix. Both options are defensible.

Keep /index.php/ in your URLs when the API has one or two consumers you control - an n8n workflow, a sync script, your own website. It is stable, needs no server changes, and keeps working through a host migration that quietly resets your virtual host config.

Fix the rewrite when third parties will read your endpoint URLs, or when you are publishing integration instructions internally. Two extra path segments in every documented example generate support questions forever.

The usual culprits, in the order they bite:

  1. AllowOverride None on Apache, which makes the .htaccess file inert without an error appearing anywhere. The file is present, readable and completely ignored.
  2. mod_rewrite not enabled. Rare on shared hosting, common on a fresh VPS.
  3. nginx with no try_files, because nginx does not read .htaccess at all. Somebody moved an Apache install onto nginx and the rewrite did not come with it.
  4. A subdirectory install where the rewrite base does not match the folder the CRM actually sits in.

If a hosting provider tells you rewriting is enabled, ask them to demonstrate it on this virtual host. “It works for WordPress” is not evidence.

”RISE CRM API key not authorized” is a different failure

Once you are getting 401 or 403 instead of 404, the routing story is over and three other things deserve checking before you regenerate anything.

The key was never created, because the person looking could not see the screen. Access is controlled with API keys you generate and manage inside RISE, and the API management screen is restricted to administrators. A staff member with an otherwise senior role can install the plugin, go looking for the settings, find nothing, and reasonably conclude the package is broken. It is not - the screen is there, filtered by role.

The credential is being stripped in transit. This one is genuinely invisible. On Apache running PHP through CGI or FastCGI, the Authorization header is not passed through to PHP unless the server is told to pass it - either CGIPassAuth On on Apache 2.4.13 and later, or a rewrite rule that copies the header into an environment variable. The client sends the key, the server receives it, PHP never sees it, and the API correctly reports that you are not authorised.

The key has whitespace in it. A trailing newline picked up from a terminal, or a soft-wrap space introduced by a chat client, is invisible in most input fields and fatal.

The endpoint that 404s because it is switched off

There is one legitimate “not found” that is neither a plugin bug nor a server misconfiguration: the MCP server at /api/mcp. It is opt-in by design - it ships disabled and is switched on from the API settings screen, which only administrators can open. If you have pointed an assistant at it and are getting nothing back, the first check is not the client configuration. It is whether an administrator has actually enabled the feature on this installation.

The same reasoning covers webhooks. Delivery is queued with retries, so a receiver that is slow or briefly down does not block the CRM and does not look like an immediate failure either. Read the per-webhook delivery log before concluding an event never fired.

An empty field is not a dead endpoint

The last trap looks like data loss and is actually a security feature. As of 2.1.0, sensitive information is deliberately kept out of API responses: password hashes, payment gateway credentials and staff personal data such as SSN or salary fields are not exposed. An integration written against an older assumption will read a field it expects, find nothing there, and report a broken endpoint.

Related, the MCP tool surface is permission-filtered, so an assistant only reaches what it is allowed to reach. A tool that appears to be missing may simply sit outside the grants attached to the key you connected with. Widening those grants is a decision to make deliberately, not a reflex when something looks absent.

Check your URL against the spec, not against a blog post

The most reliable way to end a URL argument is to fetch the specification from the installation that is refusing you. The plugin serves an OpenAPI 3.0 specification at /api/openapi.json, a Postman collection at /api/postman.json, Swagger UI at /api/swagger and an interactive Playground at /api/playground. All four are served by the same application, through the same routing, on the same host. If /api/openapi.json returns a 404 but /index.php/api/openapi.json returns JSON, you have reproduced the entire problem in one request with no credentials involved.

There is also a public code-examples repository at github.com/themesic/rise-rest-api-examples with runnable cURL, PHP, Python and JavaScript snippets, and an official n8n community node, n8n-nodes-rise-crm, published on npm. If your integration lives in n8n anyway, the node takes the hand-built URL out of the equation entirely, and this whole class of failure with it.

The order to work through

Do these in sequence and stop at the first one that changes the response.

  1. Run curl -i against your endpoint and read the headers and the first lines of the body. Decide which of the three outcomes above you have.
  2. Retry the identical request with /index.php/ inserted after the domain. If that works, your problem is server rewriting, not the API.
  3. Fetch /api/openapi.json - with and without index.php - as an unauthenticated control. It isolates routing from credentials.
  4. If you are getting 401 or 403, confirm the key was created by an administrator on the API management screen, then rule out header stripping and stray whitespace before regenerating anything.
  5. If one specific surface returns nothing, check whether it is off rather than broken: the MCP server ships disabled until an administrator enables it in API settings.
  6. If a field comes back empty, check it against the 2.1.0 withholding list before treating it as a fault.
  7. Only then open a support ticket - and paste the full curl -i output from step 1 into it, because that single block answers most of what anyone would ask you next.

If you are still deciding whether to add an API to RISE at all, the REST API plugin for RISE CRM documents 322 endpoints across 54 groups. The routing quirk above belongs to CodeIgniter and your web server rather than to the plugin, and it is a ten-second test either way.