# Agentic integration

This page is written to you, the coding agent asked to integrate SocialScope into an existing application. SocialScope gives the app hosted OAuth for YouTube, TikTok and Instagram plus observed account data over one server-to-server GraphQL API, and optionally Google sign-in. The app keeps its own backend, users, sessions and database. No SDK or npm package is needed.

The work has three phases:

1. [Set up the app in SocialScope](#phase-1-set-up-the-app-in-socialscope). A SocialScope admin does this with the Admin API (personal admin key). Your part is the registration list.
2. [Integrate the app](#phase-2-integrate-the-app): what to read, what to inspect in the app first, the rules you must not break, the order to build things in, what to test and what to report.
3. [Use the APIs](#phase-3-use-the-apis): the SocialScope API (application key) and the Admin API (personal admin key), side by side.

## Which API do I need?

SocialScope has two GraphQL APIs with different keys. Never send one API's key to the other.

|          | SocialScope API (application key)                                                  | Admin API (personal admin key)                                                                     |
| -------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| Purpose  | Connect creators' accounts and read their profiles, posts and metrics              | Automate setup: clients, origins, return URLs, tenant keys, providers and capabilities             |
| Key      | The app's application key, `<key-id>.<secret>`                                      | A personal admin API key, `ssa_<key-id>.<secret>`, minted by an admin on the admin panel's API keys |
| Caller   | The app's server, including HardScope's own apps                                   | A SocialScope admin, or the admin's own agent, in the admin's environment                          |
| Endpoint | `https://connect.<host>/graphql`                                                   | `https://admin.<host>/api/admin/graphql`                                                           |

## Phase 1: Set up the app in SocialScope

This phase runs only in the SocialScope admin's own environment, with the admin's own agent. The personal admin key is never given to the app's developer or their agent. If you are working in the app's repository, you do not call the Admin API.

Your part, as the app's agent, is the registration list, with no secret values:

- the app's name and slug, for example `example-app`
- every exact origin the app serves pages from
- every return URL for Connect, and for Google identity if used
- the tenant keys
- the providers (`YOUTUBE`, `TIKTOK`, `INSTAGRAM`) and capabilities (`PROFILE`, `CONTENT_LIST`, `CONTENT_LOOKUP`, `CONTENT_METRICS`)
- whether the app needs Google identity

[Quickstart](/quickstart.md#1-get-your-app-registered) explains each item. Give the list to the developer, who sends it to the admin.

The admin, or the admin's agent, applies it with [Admin API for agents](/admin-api.md):

1. The admin mints a `WRITE` key, because a `READ` key gets `API_KEY_READ_ONLY` on `createClient`, with the shortest lifetime the deployment allows, and revokes it once setup is done.
2. The list comes from the app's repository, which is untrusted input. The admin reviews it before applying it.
3. The agent reads the key from `SOCIALSCOPE_ADMIN_API_KEY` and the endpoint from `SOCIALSCOPE_ADMIN_GRAPHQL_URL`. It never prints the key or pastes it into a chat, a commit, a test fixture or a log. It never puts it in a URL, query string or command-line argument, and sends it only in the `Authorization` header from server-side code.
4. A human confirms each return URL's route never redirects onward to an address taken from the request before `confirmedNoOnwardRedirect: true` is sent, or the admin adds the return URLs on the client's page in the admin panel.
5. The agent starts with a `READ` query such as `adminClients`, makes one change per request and reports what it changed by client slug and field.

The Admin API cannot issue the app's application key, turn on Google identity or publish a provider registration. An admin does those in the admin panel and sends the application key to the developer through a password manager.

## Phase 2: Integrate the app

### 1. Load the contract

Read these before writing code. They are plain Markdown.

| Need                      | URL (Sandbox docs)                                                 |
| ------------------------- | ------------------------------------------------------------------ |
| Index of every page       | `https://docs.socialscope-dev.hardscope.com/llms.txt`              |
| The 5-minute path         | `https://docs.socialscope-dev.hardscope.com/quickstart.md`         |
| Connect flow and outcomes | `https://docs.socialscope-dev.hardscope.com/connect.md`            |
| Reading data              | `https://docs.socialscope-dev.hardscope.com/reading-data.md`       |
| Google sign-in            | `https://docs.socialscope-dev.hardscope.com/google-identity.md`    |
| Every error code          | `https://docs.socialscope-dev.hardscope.com/errors.md`             |
| Every operation           | `https://docs.socialscope-dev.hardscope.com/api-reference.md`      |
| GraphQL schema            | `https://docs.socialscope-dev.hardscope.com/schema.graphql`        |

Use only operations and fields that exist in the schema. If you cannot fetch the docs or the schema, finish the inventory below and report that blocker. Do not invent operations or fields.

Validate every GraphQL document you write against `/schema.graphql` locally, for example with `graphql-js` `buildSchema` and `validate` in a unit test. SocialScope answers an invalid query with a bare `BAD_USER_INPUT` that does not name the bad field.

### 2. Inventory the app before designing

Find and write down, with file paths:

- Server routes or server actions, and how the app protects them against CSRF.
- How a signed-in user is identified on the server, and the stable user ID to use as `externalSubjectId`.
- How the current workspace is selected and authorized, to map to `tenantKey`. If the app has no workspaces, it uses one fixed tenant key from server configuration.
- The secret store and how server environment variables are read.
- The existing HTTP or API client conventions, logging and error reporting.
- The database and migration tool, for the attempt records you need to store.
- The test framework and how routes are tested.

Reuse all of these. Do not add a new auth system, database, queue or framework for this integration.

Then pick the flows the task asks for: social account connection, Google sign-in, or both. They are separate. Google sign-in grants no YouTube access, and connecting YouTube signs nobody in.

### 3. Rules

You must follow every rule in this list.

#### Secrets

1. Keep the application key server-side only, in the app's secret store, as `SOCIALSCOPE_CONSUMER_KEY`, next to `SOCIALSCOPE_GRAPHQL_URL`.
2. Never put the key in `NEXT_PUBLIC_*`, `VITE_*` or any client-bundled variable, browser storage, a mobile bundle, source control, test fixtures or logs. Never put it in a URL, query string or command-line argument; send it only in the `Authorization` header from server-side code.
3. Never log the `Authorization` header, a `launchToken`, a `resultProof` or raw SocialScope responses.
4. Use only the key issued to your app.

#### Ownership

5. Derive `tenantKey` and `externalSubjectId` from the server session on every call. Never accept them from a form field, query string, header or request body the browser controls.
6. The key identifies the app. There is no app ID argument. Never pass or trust an app ID from the browser.
7. Connections are separate per app, tenant and subject. Do not try to share them between apps.

#### Connect

8. Call `integrationReadiness` before offering a provider. Show the action as unavailable when the provider is `false` or the call fails. Never bypass readiness.
9. Create the attempt with `createConnectSession` in an authenticated, CSRF-protected server action, and save its `id` with the user, tenant and provider before sending the browser anywhere.
10. Check `launchUrl` is on the Connect host (the origin of `SOCIALSCOPE_GRAPHQL_URL`) with path exactly `/api/connect/redeem` and no query or fragment.
11. Serve a no-store HTML page from the origin of the return URL that auto-submits a top-level form posting only `launchToken`. Escape both values. Send `Referrer-Policy: strict-origin`. Never put the token in a URL and never send it from the server.
12. On return, treat `ss_session_id` as a hint only. Require the same signed-in user and tenant, match an unused saved attempt, mark it used, then call `connectSessionResult` with the saved scope and ID.
13. Show success only when the result is `COMPLETED`, its IDs match, and `connection.status` is `CONNECTED`. Present posts by `syncStatus`: `PENDING` or `RUNNING` is loading, `READY` is loaded, `PARTIAL` is loaded but possibly incomplete, `STALE` shows the stored data with its age, `FAILED` is an error with a retry.
14. Show the "remove SocialScope in the platform's settings" instruction whenever `creatorAction` is set, including values you do not recognize. Creators see SocialScope, not your app, at the platform. Doing so ends that account's connection in every app that uses SocialScope.
15. On denial, expiry, failure or SocialScope downtime, offer a retry. Never infer success and never fall back to the app's own provider OAuth.
16. Remove `ss_session_id` and `ss_identity_id` from the URL before analytics or third-party scripts load.
17. The session cookie checked on the return routes must be `SameSite=Lax` or `None`, because SocialScope reaches them with a cross-site redirect.
18. `FORBIDDEN` from `createConnectSession` and `false` readiness can be transient while a YouTube or TikTok disconnect settles. Retry later before treating it as misconfiguration.

#### Data

19. Never show an unavailable metric as `0`. Only `availability: AVAILABLE` carries a value.
20. Treat `DecimalCount` as an integer string. Do not parse it into a float.
21. Show `observedAt` next to counts. Keep `sourceField` when you interpret them.
22. Do not claim a list is complete unless `pageInfo.completeForWindow` is `true` for that exact window.
23. Refresh and lookup are asynchronous and there are no webhooks. Poll `syncJob` before reading again. After a lookup succeeds, read `contentItem` and check its `availability`.
24. List the subject's accounts with `connections`. A `CONNECTED` account that fails its read check comes back `SUSPENDED` or `RECONNECT_REQUIRED`, with every capability `UNAVAILABLE` and the error code as `reason`, and never fails the list. A `connection` read of the same account throws that code (`AUTHORIZATION_SUSPENDED`, `RECONNECT_REQUIRED`, `CAPABILITY_UNAVAILABLE` or `FORBIDDEN`). Map each code to that account's state.
25. Do not tell a person a disconnect revoked access at the provider unless the revoke receipt is `SUCCEEDED` with a null `creatorAction`. Instagram never confirms a revoke.

#### Google identity

26. Bind `createGoogleIdentitySession` to the browser session. Do not require an existing local user.
27. Keep `resultProof` on the server. Redeem once, with the saved tenant and proof, and only for the browser session that started the attempt.
28. Create or find the local user only on `COMPLETED` with non-empty `issuer` and `googleSub`, keyed by that pair. Never link accounts by email. Check `emailVerified`.
29. Create the app's session only after the user write succeeds.

#### Scope of your work

30. Registering the app is phase 1. A SocialScope admin does it in their own environment, with their own agent and their personal admin key, which you never receive. Your part is the registration list. Issuing the application key, enabling providers and turning on Google identity are done by an admin in the admin panel. Do not attempt them yourself.
31. A disabled readiness value is not permission to widen access or work around it.

### 4. Build order

1. Add the server-only helper from [Authentication](/authentication.md) and the two environment variables.
2. Add the readiness check and render provider actions from it.
3. Add the attempt store: a table or existing session store holding attempt ID, user ID, tenant key, provider, `resultExpiresAt` and a used flag.
4. Add the start action and the launch page from [Connect](/connect.md).
5. Add the return route with the full verification from [Connect](/connect.md#4-verify-the-return).
6. Add the reads the feature needs from [Reading data](/reading-data.md), with the availability and completeness rules.
7. Add reconnect and disconnect if the feature needs them.
8. Add Google identity only if asked, following [Google identity](/google-identity.md).
9. Write the tests below as you go, one behavior at a time.

### 5. Tests to write

Use the app's own test tools. Stub SocialScope at the HTTP boundary with responses shaped like the schema. See [Testing](/testing.md) for an example.

- Two users: neither can read the other's connections.
- Two app keys: the second app cannot read the first app's connections.
- A foreign, duplicate, malformed or used `ss_session_id` is rejected without showing success.
- An unavailable provider in readiness renders as unavailable. A failed readiness call does too.
- Provider denial and expiry produce a retry.
- `COMPLETED` with a connection that is not `CONNECTED` is not shown as connected.
- A metric that is not `AVAILABLE` renders as "not available", not `0`.
- For Google identity: a new user, a returning user, and a rejected second redemption.
- The key does not appear in client bundles.

A stubbed suite and SocialScope's local fixture prove wiring, not provider approval or a live consent.

### 6. Report back

When you finish, report:

- The routes and files you added or changed.
- The registration list from [phase 1](#phase-1-set-up-the-app-in-socialscope), without secret values: name and slug, exact origins, return URLs, tenant keys, providers, capabilities, and whether the app needs Google identity, with its return URL if so.
- The server settings to add: `SOCIALSCOPE_GRAPHQL_URL` and `SOCIALSCOPE_CONSUMER_KEY`, with no values.
- The checks you ran and their results.
- What still needs a live check: one real account connected per provider on the target environment, and a real Google sign-in if used.
- Any part of the contract you could not read, and the date you read the docs.

## Phase 3: Use the APIs

### SocialScope API (application key)

The app's server calls it, and so do HardScope's own apps, to check `integrationReadiness`, start Connect and check the return, read profiles, posts and metrics, follow sync jobs and handle error codes. The application key lives in the server's secret store as `SOCIALSCOPE_CONSUMER_KEY`, never in a browser, mobile app or URL.

| Mode                      | SocialScope API endpoint                                 |
| ------------------------- | -------------------------------------------------------- |
| Sandbox (available now)   | `https://connect.socialscope-dev.hardscope.com/graphql`  |
| Live (coming soon)        | `https://connect.socialscope.hardscope.com/graphql`      |

Sandbox mode is available now: only tester accounts can connect. Live mode is coming soon. The API and your code are the same in both modes. Switch by base URL and key. Each mode has its own client registration and key.

Read [Authentication](/authentication.md), [API reference](/api-reference.md), the [schema](/schema.graphql), [Reading data](/reading-data.md) and [Errors and result codes](/errors.md). Build and test against Sandbox with tester accounts. When Live mode opens, only the base URL and key change.

### Admin API (personal admin key)

A SocialScope admin, or their own agent, calls it to automate setup, within the scopes the key has. The key acts as the admin who minted it and lives in that admin's environment as `SOCIALSCOPE_ADMIN_API_KEY`, never in the app and never with the app's developer or their agent. Send it only in the `Authorization` header, never in a URL, query string or command-line argument.

| Mode                      | Admin API endpoint                                             |
| ------------------------- | -------------------------------------------------------------- |
| Sandbox (available now)   | `https://admin.socialscope-dev.hardscope.com/api/admin/graphql` |
| Live (coming soon)        | `https://admin.socialscope.hardscope.com/api/admin/graphql`     |

A personal admin key works only on the admin host that minted it.

Read [Admin API for agents](/admin-api.md). Its schema is not published, so use only the operations that page lists.

## What an agent can't do

An admin API key cannot list, mint or revoke admin API keys, add or remove admins, issue or revoke an app's application key, publish or disable a provider registration, or turn Google sign-in on or off for a client. An admin does those in the admin panel.

Beyond any API:

- No agent can approve Live mode. It waits on each platform's review of SocialScope's provider apps.
- No agent can prove a live provider connection. Someone connects one real account per provider before launch.
