Your application processes personal data. Under India's **Digital Personal Data
Protection Act, 2023**, somebody has to be named as the **Data Fiduciary** —
the party that decides why and how that data is processed — and that party has
to publish a **grievance officer** a Data Principal can complain to (s.13).

On Connect, that party is the platform operator. Your application is a **Data
Processor** acting on their notice. So the channel your privacy page prints,
and the one your consent capture points a user at, is theirs.

<Callout title="This is a fact about the platform, not about a workspace">
  `getCompanyProfile()` takes no `workspaceId` and no session. The answer is
  identical for every tenant and every caller, because it describes who
  operates the thing your application runs on.
</Callout>

## Read it, don't copy it

The obvious way to ship a grievance address is configuration:

```bash title=".env.local"
# Don't. This is the shape the endpoint exists to replace.
APP_DATA_FIDUCIARY_NAME="..."
APP_GRIEVANCE_OFFICER_NAME="..."
APP_GRIEVANCE_OFFICER_EMAIL="..."
```

It works, and then an officer resigns.

Every application that copied the old address is now publishing a redressal
channel that reaches nobody — silently, with no error, no failing test and no
alert, for as long as it takes a human to notice. A statutory contact is
precisely the piece of configuration that must not be able to drift on its
own, so it is read at runtime instead:

```ts title="app/privacy/page.tsx"
import { connect } from "@/lib/connect";

const profile = await connect.platform.getCompanyProfile();

profile.legalName;                 // the Data Fiduciary to name
profile.brandName;                 // for prose the registered name reads oddly in
profile.registeredAddress;
profile.legalJurisdiction;         // the venue a governing-law clause names
profile.grievance.officerName;
profile.grievance.officerEmail;    // where a complaint goes
profile.grievance.hours;
profile.grievance.responseDays;    // the window the notice commits to
```

The identity and the whole grievance block are non-nullable. There is nothing
here for you to write a fallback for — and that is the point, because a
fallback is the stale copy again wearing a different hat.

`website`, `gstNumber` and `legalJurisdiction` are the three that can be
`null`, and they are *optional facts*, not unconfigured ones — omit the line
rather than substituting something. `legalJurisdiction` in particular is not
the registered address: an entity registered in one state routinely names
another as the venue for disputes, and reaching for `registeredAddress`
instead is a mistake two applications here made, differently.

<Callout type="warn" title="A 503 is the operator's problem, not yours">
  There are no server-side defaults behind this endpoint. If the platform
  operator has not filled in their company details it answers `503
  unavailable` rather than inventing an answer — because inventing one is the
  entire failure this endpoint exists to remove, and doing it one layer down
  would be the same mistake with better manners. Render your page without the
  block. Do not add a fallback of your own "just for outages": that is a copy,
  and it goes stale exactly like the last one did.
</Callout>

## Rendering it

The wording ships with the SDK, so a user comparing two applications' privacy
pages does not find two different accounts of who their Data Fiduciary is:

```tsx title="app/privacy/page.tsx"
import { ConnectGrievanceNotice } from "@cosmoops/connect-sdk/client";

<ConnectGrievanceNotice applicationName="CosmoOps CRM" />
```

It renders `null` while loading and `null` on failure rather than a skeleton or
an error box. A red error box in the middle of a legal notice reads as the
notice itself being broken, and the address is on the public web either way.
Call `useConnectCompanyProfile()` directly if you want to handle those states.

The hook does not require a session, unlike every other hook in `/client` —
it is the one caller of `useConnectResource`'s `{ requiresSession: false }`.
That is deliberate: the moment a grievance channel most needs to be legible is
on a public page a signed-out visitor is reading. So keep the route out of
`ConnectAccessGate` and mark it `gateExempt` in your nav; a redressal channel
behind a login wall is the shape a regulator objects to.

The component is live on
[Screens](/docs/ui/screens#the-privacy-notice-you-do-not-have-to-write), signed
out, rendering this endpoint.

## What is still yours

Reading the platform's notice does not make the SDK your compliance layer.
These stay your application's problem, because they are about data you decided
to collect:

| Yours | Why |
| --- | --- |
| **Consent capture** | You chose the purposes. Record what was agreed, when, on which notice version, and over which channels. |
| **The consent ledger** | The Act puts the burden of *demonstrating* consent on the Fiduciary. Current state ("opted out") does not say when or on what notice, so log events and never mutate them. |
| **Withdrawal** | Must be as easy as granting. If consent took one click, so does taking it back. |
| **Access, correction, erasure** | A Data Principal can ask. Your store, your export, your deletion. |
| **Retention** | Erase once the purpose is served. That is a judgement, not a timer — flag for review rather than running an unattended purge over a customer's contact book. |

A worked example of all five is an append-only consent ledger: a collection
declared `appendOnly` in your `storeSchema`, with no update and no delete path
in your own code either. A ledger a bug can rewrite is not evidence — see
[the store](/docs/api/data) for what `appendOnly` refuses, and to whom.

## Caching

The profile changes about never, and the response carries
`s-maxage=3600, stale-while-revalidate=86400`. Cache it in your own layer too
if you render it on a hot path.

Do not cache it for a release cycle. An hour is the deliberate ceiling on how
long a *corrected* grievance contact can keep being published wrong, which is
the failure this whole arrangement exists to bound.

## Prerequisite

The endpoint needs the **`company:read`** scope on your API key. Existing keys
do not carry it — tick it in the Console's Integration tab, or re-run your
application's seed script, which widens scopes in place.