What has been changed in this org’s settings
const url = 'https://api.sloose.com/orgs/org_9f3c/config/changes?connector=con_8f2a&page=modules&limit=50';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.sloose.com/orgs/org_9f3c/config/changes?connector=con_8f2a&page=modules&limit=50' \ --header 'Authorization: Bearer <token>'Newest first, a page at a time — limit and cursor, answering nextCursor. One entry per changed VALUE rather than per request, because that is the shape of the question it answers — “who turned Contacts off, and when?” The entries of one request share a registryVersion, which is how to put them back together.
Scoped by page. A connection’s own pages — its modules, fields and write pause — are the connection ?connector= names, since an org with two CRMs has two histories and Contacts in both; handing over the pair interleaved would let a reader attribute one CRM’s change to the other. Every other page is the org’s and is returned whatever is named — among them Connections, whose rows each carry the connection they are about in connectorId. Without ?connector=, a connection’s own pages give nothing.
This is the org’s own record of its settings and its billing, and it is erased with the org. Most rows are its own people’s, and via says when one is not: on Plan and Usage & credits a change can come through Stripe, the schedule — or Sloose support, when a staff member gives the org a trial or credits, recorded here as staff with nobody named. Everything else we do to an org is staff_actions, which is not this, names the staff member, and outlives it.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The org id the session was issued for. A token for one org can never read another.
Example
org_9f3cThe org id the session was issued for. A token for one org can never read another.
Query Parameters
Section titled “Query Parameters”The connection whose history to include beside the org’s own, by id. Absent: the org’s own changes alone — except that asking only for a connection’s pages (modules, fields) without one is 400 CONNECTION_REQUIRED, listing the connections.
Example
con_8f2aThe connection whose history to include beside the org’s own, by id. Absent: the org’s own changes alone — except that asking only for a connection’s pages (modules, fields) without one is 400 CONNECTION_REQUIRED, listing the connections.
Only this settings page’s changes. May be repeated — one section can show several pages’ history, which is what a connection’s modules do with modules and fields.
How many to return, at most 200; 50 when absent. Anything but a whole number above zero — 0, negative, a fraction, not a number — is the default, never an error.
Example
50How many to return, at most 200; 50 when absent. Anything but a whole number above zero — 0, negative, a fraction, not a number — is the default, never an error.
The nextCursor of the page before this one; absent for the first page. Opaque: it names a place in the list’s (createdAt, id) order, and a malformed one is 400 INVALID_CURSOR.
The nextCursor of the page before this one; absent for the first page. Opaque: it names a place in the list’s (createdAt, id) order, and a malformed one is 400 INVALID_CURSOR.
Responses
Section titled “Responses”A page of changes, newest first.
object
object
The settings page this change belongs to.
module.enabled, settings.timezone, field.excludeFromAi, bundle, member.role, connection.name, connection.disconnected, …
What it was about, by name: the module or field, the person (People), the connection (Connections); null when it was the org’s own setting.
What it was — null when that was not knowable, which a whole-registry replacement and a bundle push are. Never a guess.
Whose proposal the new value was, when it was not the author’s own: ai when the person used a value the AI proposed (Ask AI on a module’s page). The author is still that person. Null for a value they gave themselves, and for every change recorded before this was.
The version this change produced. The changes of one request share it.
Who, as a person — null when an org API token made the change, since a token is the org’s and outlives whoever minted it, and null for the billing authors in via other than a person’s. An MCP bearer keeps its person.
Their name as they gave it, resolved from users. Null when no person made the change — a token, or one of the billing authors in via. It does NOT go null for somebody who has left the org — that is the case the join exists for, and their users row outlives the membership.
Their address, which is what an administrator recognises; null as userName is. Resolved through a LEFT join on purpose: somebody whose membership has been removed or revoked is no longer in GET /members, so a client that looked them up there would render the change being investigated as nobody — while this still names them.
How it arrived: a person’s session, an org API token or an MCP bearer — or, on Plan and Usage & credits, with nobody’s request behind it: stripe (the Customer Portal, Stripe’s dashboard, a subscription Stripe ended), schedule (a trial or a paid-up period running out, or a downgrade taking effect) and staff (Sloose support, from the backoffice). A plan bought through Checkout and a credit pack are the person who started the checkout, though Stripe’s webhook applies them.
WHICH token, when via is token. Without it a token-authored change can only say “some token”, which is no answer when an org has several integrations.
That token by the name whoever minted it chose. A REVOKED token still has one — revoking deletes nothing — so a change made before the revocation goes on naming it.
The connection this change is about: the one whose registry it changed (modules, fields, its write pause), or, on Connections, the connection itself — added, reconnected, renamed, listed or disconnected. Null for a change about the org alone. Two connections can hold modules of the same name, so without this their changes are indistinguishable. Which rows a request is given is decided by PAGE, not by this column: see the route.
Pass as cursor for the next page; null when this was the last.
Example
{ "changes": [ { "proposedBy": "ai", "via": "session" } ]}Only a connection’s pages were asked for, and no connection was named (code: CONNECTION_REQUIRED, with connections), or cursor is malformed (code: INVALID_CURSOR).
The error envelope every non-2xx answer uses.
object
Human-readable explanation.
Machine-readable reason. Absent on a few legacy 400s.
Examplegenerated
{ "error": "example", "code": "example"}No bearer, or one that is expired, revoked or no longer resolves to a member.
The error envelope every non-2xx answer uses.
object
Human-readable explanation.
Machine-readable reason. Absent on a few legacy 400s.
Examplegenerated
{ "error": "example", "code": "example"}The bearer’s role is too low, or it was issued for a different org.
The error envelope every non-2xx answer uses.
object
Human-readable explanation.
Machine-readable reason. Absent on a few legacy 400s.
Examplegenerated
{ "error": "example", "code": "example"}?connector= names a connection this person may not use: another org’s, or another person’s (code: CONNECTION_NOT_FOUND).
The error envelope every non-2xx answer uses.
object
Human-readable explanation.
Machine-readable reason. Absent on a few legacy 400s.
Examplegenerated
{ "error": "example", "code": "example"}