# AppRefer access for AI agents

AppRefer is one SDK and one dashboard for mobile attribution, subscriptions, in-app support and analytics (iOS, Android, Flutter, React Native). Full reference: https://apprefer.com/llms-full.txt (index of every docs page as markdown: https://apprefer.com/llms.txt)

This page tells an AI agent how to get an AppRefer account for the person it works for. The agent never handles a password: the human confirms with one Google sign-in and a code.

## Let your agent sign up

POST /api/agent/workspaces (public, JSON):
{ "email": "owner@example.com", "company": "Example Labs", "appName": "Example App", "agent": "Your agent" }
email and company are required. appName and agent are optional. Returns 201:
{ "workspace_id": "org_…", "api_key": "ak_live_…", "claim_url": "https://apprefer.com/activate?code=XXXX-XXXX", "user_code": "XXXX-XXXX", "expires_at": "<ISO timestamp, seven days>", "mcp_url": "https://apprefer.com/api/mcp", "test_mode": true }

Save api_key securely on the server; it is returned once, hashed at rest, scoped to this organization and expires after seven days even after claim. Never put it in an app, browser bundle or log. Despite its ak_live_ prefix, it can only set up in test mode until the human claims and live access opens. Use pk_test_ SDK keys. GET /api/agent/workspaces/{workspace_id} with Authorization: Bearer <api_key> returns { "status": "unclaimed" | "claimed_pending_approval" | "live" | "expired" }. The expired key can only read this status until cleanup removes it; it cannot operate on the workspace.

Give claim_url and user_code to the human. They sign in with the matching verified Google email, review the apps built, then claim ownership or choose “This wasn't me” to delete the workspace. Claim joins the waitlist; live mode opens on approval, or immediately when signup is open. The agent can keep configuring sandbox data meanwhile. Unclaimed workspaces are removed after seven days. One open workspace per email; three creations per IP per day; up to three apps, twenty links and fifty products while unclaimed. Validation errors are 400 invalid_request, email conflict 409 workspace_exists, rate limit 429 rate_limited, storage failure 503 unavailable.

Test mode permits apps, sandbox SDK data, tracking links, billing catalog (products, entitlements, offerings, placements, targeting, experiments), sandbox analytics and help articles. Ad integrations and store credentials, live SDK access, Support AI/delivery settings, web events, web purchases and erasure are refused. Signed Apple sandbox receipts can be verified without store credentials once the app bundleId is set through update_app; production receipts with pk_test_ are rejected. Google Play receipt verification still requires store credentials after claim and approval. Xcode-local purchases unlock on-device only under the existing SDK behavior. Team invitations and seats require claim. No platform admin key is given to a customer.

The existing POST /api/agent/access-requests flow below remains an alternative for requesting account access without creating a workspace. It never issues credentials.

## Connect over MCP (OAuth)

POST https://apprefer.com/api/mcp accepts organization API keys and OAuth access tokens, alongside operator admin keys. Use Authorization: Bearer <credential>. GET returns 405; each POST is stateless JSON-RPC. tools/list shows only tools allowed by the credential. The company always comes from that credential: omit orgId; appId/linkId must belong to it. Platform tools (list_orgs, cross-company portfolio, waitlist administration) are unavailable to customer agents.

A 401 includes WWW-Authenticate: Bearer resource_metadata="https://apprefer.com/.well-known/oauth-protected-resource/api/mcp". Protected-resource metadata is also at /.well-known/oauth-protected-resource; authorization-server metadata is /.well-known/oauth-authorization-server. resource must be https://apprefer.com/api/mcp at authorization and token exchange. Use HTTPS Client ID Metadata Documents (client_id, client_name, redirect_uris, token_endpoint_auth_method: none); public DNS, bounded fetch and no redirects are enforced. POST /api/oauth/register is the per-IP rate-limited public-client fallback with client_name and redirect_uris (HTTPS or exact localhost loopback callback); no client secret is issued.

Open /oauth/authorize with client_id, response_type=code, redirect_uri (exact registered value), scope (space-separated), code_challenge, code_challenge_method=S256, resource, and state. The human signs in, picks a company they belong to and allows or cancels. Claim a trial workspace first to make it selectable. The response includes iss and state. POST form-encoded /api/oauth/token with grant_type=authorization_code, code, client_id, redirect_uri, code_verifier, resource. Codes expire in ten minutes and work once. Access tokens are opaque, expire after one hour and are audience-bound to MCP. Refresh: grant_type=refresh_token, refresh_token, client_id, resource. Replace the refresh token every time; replay revokes the entire connection. Connections have a maximum thirty-day lifetime. Reconnect after that. POST /api/oauth/revoke with token and client_id, or use Settings → Connected agents to revoke immediately. Removing membership or write privileges also ends token authority. Tokens are hashed at rest.

Scopes: apps:read (info, get_org, list_apps, get_app, readiness), apps:write (create/update apps); links:read/write; integrations:read/write; analytics:read (stats, overview, campaigns, events, attributions); skan:read; billing:read (catalog, subscription metrics, experiments), billing:write (catalog changes); support:agent (list/upsert/publish help articles). Viewer/member OAuth connections may request read scopes only. Trials remain test-only regardless of credential scopes.

Risky customer MCP tools (delete_link, erase_user, update_billing_config, integration changes, Support AI auto) return { "approval_required": true, "approval_url": "https://apprefer.com/approve/<secret>" } inside result.content[0].text. A signed-in company admin approves the exact action; then repeat the same tool and arguments with approval_id set to the final URL segment. Approval expires after 24 hours, is bound to this credential and action and works once. Changing arguments or retrying a consumed approval fails. erase_user requires users:erase, appId, appUserId, requestId and explicit environment (all|live|sandbox); update_billing_config requires integrations:write, appId and config (same shape as REST billing config). Both require a human approval. Trial erasure and store configuration are refused.

## Access request only: summary

1. POST https://apprefer.com/api/agent/access-requests with the human's email.
2. Show the human the `verification_uri_complete` link and the `user_code`.
3. The human opens the link, signs in with Google (same email) and taps Confirm.
4. Poll GET https://apprefer.com/api/agent/access-requests/{request_id} every `interval` seconds.
5. `confirmed` = on the waitlist (approved by hand right now); keep polling, less often, until `approved`.
6. After approval the human creates an organisation API key; the agent uses the REST API with it.

## 1. Request access

```http
POST https://apprefer.com/api/agent/access-requests
Content-Type: application/json

{
  "email": "founder@example.com",
  "company": "Example Labs",
  "appUrl": "https://apps.apple.com/app/id0000000000",
  "note": "Attribution for Meta ads and subscriptions",
  "agent": "Claude Code"
}
```

Fields: `email` (required; the human's Google account email), `company` (optional, up to 120 characters), `appUrl` (optional http(s) URL, up to 300), `note` (optional, up to 1000), `agent` (optional; your client's name, up to 80, shown to the human).

```http
HTTP/1.1 201 Created
Content-Type: application/json

{
  "request_id": "arq_...",
  "user_code": "BDFG-HJKL",
  "verification_uri": "https://apprefer.com/activate",
  "verification_uri_complete": "https://apprefer.com/activate?code=BDFG-HJKL",
  "expires_in": 1800,
  "interval": 5
}
```

- `request_id` is secret to you: keep it, don't show it to anyone. Only you can poll with it.
- `user_code` is for the human. It expires after `expires_in` seconds (30 minutes) and works once.
- Errors: `400 invalid_request` (bad email or field; `message` says which), `413` body over 8 KB, `429 rate_limited` (5 requests per hour per IP address), `503 unavailable` (retry later).

## 2. What the human does

Tell the human, in your own words:

> Open https://apprefer.com/activate?code=BDFG-HJKL, sign in with Google as founder@example.com, check the code is BDFG-HJKL and tap Confirm.

- They must sign in with the Google account whose email you sent. A different account is refused.
- The page shows your `agent` name, the email, the company and the code. If they didn't ask for this, they tap "This wasn't me" and the request is denied.
- Confirming does not give you a key or any data. It puts their account on the AppRefer waitlist.

## 3. Poll for the result

```http
GET https://apprefer.com/api/agent/access-requests/arq_...

HTTP/1.1 200 OK
{ "status": "pending", "email": "founder@example.com", "expires_in": 1712 }
```

| status | meaning | what to do |
|---|---|---|
| `pending` | The human hasn't confirmed yet. | Poll again after `interval` seconds. |
| `confirmed` | The human confirmed; the account is on the waitlist. | Tell the human. Poll every few minutes (or stop and check later): approval is by hand. |
| `approved` | The account may create a company. | Go to "After approval". |
| `denied` | The human said it wasn't them. | Stop. Don't retry without asking the human. |
| `expired` | The code wasn't confirmed in 30 minutes. | Start a new request if the human still wants access. |

- Poll at most once every `interval` seconds. Faster polls get `429 { "error": "slow_down", "interval": 10 }`: wait that long.
- `404 not_found`: unknown request id, or an old request that was removed. Start again.

## Waitlist

AppRefer is waitlist-only right now: accounts are approved by hand, a few teams at a time, so `confirmed` can last days. Approval doesn't happen faster by polling more. Keep the `request_id` and check back; a confirmed request can be polled for 30 days.

## After approval

There is no automatic credential yet. Ask the human to:

1. Sign in at https://apprefer.com/login with the same Google account and create their company.
2. Open API keys in the dashboard (account menu at the bottom of the sidebar) and create an organisation key with only the scopes you need. The scopes are listed at https://apprefer.com/docs/api#scopes.
3. Put the key (`ak_live_...`) in an environment variable or secret store you can read, for example `APPREFER_API_KEY`.

Then use the REST API at https://apprefer.com/api/v1 with `Authorization: Bearer $APPREFER_API_KEY`. The SDK set-up for the app itself is in https://apprefer.com/llms-full.txt. MCP access for customer keys (OAuth) is coming; today the MCP endpoint is only for whoever runs an AppRefer deployment.

## Safety rules

- Never ask the human for their password, a Google sign-in code or a one-time code from their email. AppRefer never needs them.
- Never ask the human to paste an API key into a chat, and never print a key, commit it, or put it in an app or web page. Read it from an environment variable or secret store.
- Ask for the fewest scopes that do the job (a read-only key for reporting and checks), and tell the human to revoke the key when the job is done.
- Only request access for the person you work for, with their email, when they asked for it.
- Ask the human before changing ad network, billing or support settings, deleting anything or erasing users.
