# Quickstart

This guide takes you from partner credentials to a working integration: a seller connects your application, you receive tokens, you call the API, and you receive webhooks.

## Before you start

Demarky registers your application and sends you:

| Item | Notes |
|---|---|
| `client_id` | Public identifier, shaped `app_…` |
| `client_secret` | Shown to Demarky staff once and sent to you once. Store it encrypted. |
| Granted scopes | The ceiling for every seller grant. Requesting anything outside it fails. |
| Registered redirect URIs | Matched **exactly**, including scheme, host, port, and path |

Send Demarky your redirect URI before you start: an unregistered value is rejected at the first step.

## 1. Send the seller to Demarky

Your application starts the flow. Generate a fresh `state` and a PKCE `code_verifier` per attempt, store both against the browser session, and redirect the seller's browser — this is a full-page navigation, not a background request.

```
https://api.demarky.ai/oauth/authorize
  ?client_id=app_0123456789abcdefghjkmnpqrs
  &redirect_uri=https://partner.example/oauth/callback
  &response_type=code
  &scope=products:read%20pages:write
  &state=ac9f7e2b13c84f0f1d7a
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
```

| Requirement | Rule |
|---|---|
| `state` | At least 16 characters of randomness; returned unchanged on success and on error |
| `code_challenge` | Base64url SHA-256 of your verifier, 43–128 characters |
| `code_challenge_method` | `S256` only; plain PKCE is rejected |
| `scope` | Space-separated, at least one, all within your registered scopes |

The seller signs in to Demarky, reviews the permissions, and approves. Their browser returns to your `redirect_uri` with `code` and `state`. If they decline, it returns with `error=access_denied` instead.

## 2. Exchange the code

Validate that `state` matches what you stored, then exchange the code from your server. The code is single-use and expires in five minutes.

```bash
curl -X POST https://api.demarky.ai/oauth/token \
  -u "$DEMARKY_CLIENT_ID:$DEMARKY_CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=dmk_ac_live_xxx \
  -d redirect_uri=https://partner.example/oauth/callback \
  -d code_verifier=your_original_pkce_verifier
```

```json
{
  "access_token": "dmk_at_live_xxx",
  "token_type": "Bearer",
  "expires_in": 600,
  "refresh_token": "dmk_rt_live_xxx",
  "scope": "products:read pages:write",
  "account_id": "acct_0123456789abcdefghjkmnpqrs",
  "application_id": "app_0123456789abcdefghjkmnpqrs",
  "installation_id": "ins_0123456789abcdefghjkmnpqrs"
}
```

## 3. Store the installation

| Field | Why you need it |
|---|---|
| `installation_id` | Your primary key for this connection; stable across re-consent |
| `account_id` | Goes in the path of every resource request |
| `access_token` + expiry | Expires in 10 minutes |
| `refresh_token` | Rotates on every use — always persist the newest one |
| `scope` | What this installation may actually do |

There is no endpoint that returns these later. If you lose them, the seller has to reconnect.

## 4. Call the API

```bash
curl -H "Authorization: Bearer dmk_at_live_xxx" \
  https://api.demarky.ai/v1/accounts/acct_0123456789abcdefghjkmnpqrs/products
```

A `401` with `invalid_token` means the token expired or the seller disconnected: refresh once and retry, and treat a second failure as disconnected. A `403` with `insufficient_scope` is permanent for this installation — retrying will not help, because only a new consent can widen a grant.

## 5. Keep the connection alive

```bash
curl -X POST https://api.demarky.ai/oauth/token \
  -u "$DEMARKY_CLIENT_ID:$DEMARKY_CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token=dmk_rt_live_xxx
```

Three rules decide whether your integration survives in production:

- **Refresh one request at a time per installation.** Refresh tokens rotate, and presenting a spent one is treated as theft: Demarky invalidates the entire installation. Two concurrent refreshes are enough to trigger it, so collapse them behind a per-installation lock.
- **Persist the new refresh token before you use the new access token.** If that write is lost, your next refresh presents a spent token and the installation is gone.
- **Build a reconnect path.** A refresh-token family is capped at 90 days from first issue, regardless of activity. Every seller passes through consent again at least quarterly. An `invalid_grant` response is terminal: stop retrying and prompt the seller to reconnect.

## 6. Receive webhooks

Webhook endpoints belong to your application, not to individual sellers, and you manage them yourself with your client credentials. **Deploy your receiver first** — Demarky posts a signed challenge to the URL during registration, and an endpoint whose challenge fails stays in `pending_verification`.

```bash
curl -X POST https://api.demarky.ai/v1/developer/webhook-endpoints \
  -u "$DEMARKY_CLIENT_ID:$DEMARKY_CLIENT_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://partner.example/webhooks/demarky","events":["lead.created"]}'
```

The challenge arrives as a `POST` with header `Demarky-Event-Type: endpoint.verification` and a JSON body containing a `challenge` nonce. Answer `200` and echo the nonce back, either as the raw body or as `{"challenge": "…"}`. On a first registration you cannot verify its signature: the endpoint secret is created for that challenge and only returned to you in the response you are still waiting on. Verify signatures on every delivery afterwards, and on re-verification and secret rotation.

The response contains `secret` exactly once. See [Webhooks](/webhooks) for the signature scheme, the event catalogue, and delivery semantics.

## 7. Disconnecting

```bash
curl -X POST https://api.demarky.ai/oauth/revoke \
  -u "$DEMARKY_CLIENT_ID:$DEMARKY_CLIENT_SECRET" \
  -d token=dmk_rt_live_xxx
```

Revoking a refresh token ends the family but leaves already-issued access tokens valid until they expire, so revoke both if that matters to you.

A seller can also disconnect from their Demarky dashboard, and there is no webhook for it. You discover it the same way you discover an expired grant: calls return `401` and the refresh returns `invalid_grant`. Treat that pair as an uninstall and clean up.
