The key difference between 'changed' and 'ok' in Ansible is the internal agreement between all modules on what is 'ok' and what is 'changed'.
When a module think that it's action changed something (e.g. a state of a subject before module execution and a state after are different), it need to report 'changed' to Ansible. If there were no meaningful changes (definition of 'meaningful' is left to the module), than it reports 'ok'. Sometime modules can detect in advance if changes are needed (example: There is no need to create a directory, as it already exists), sometime it can be detected only after execution of the action (f.e. some application reported 'not changed' to the attempted reconfiguration).
Now let's look to url module. How can url module to guess if something have been changed on a remote http/s server after request? The truth is, that it can sometimes. If server answered with something like 'updated', the module can report 'changed' back. But what to do if server answer is '200 OK'? So, there is no 'changed' here.
What to do?
Ansible have a feature: you can override the default module 'changed' status by stating condition for 'changed'.
Example
This is an example from my real configuration for API server:
uri:
method: POST
url: http://{{ api_address }}:{{api_port }}/config
body_format: raw
headers:
Content-Type: text/plain
body: '{{ {"global_config": global_config} |to_yaml }}'
status_code:
- 200
- 304
- 201
register: config_post
changed_when: config_post.status == 200 or config_post.status == 201
As you can see we have an agreement that '200 or 201' is 'changed', and '304' is not changed (but ok). This agreement is supported by server and by Ansible role.
For less precise situation, you can just say changed_when: True and your task will always be 'changed' (except for failures and skips).