# Integrating IssueProbe

You are integrating IssueProbe into a project. Follow this in order.

If you have not yet decided whether this service fits, read https://issueprobe.com/evaluate.md first.

The full reference for each call is https://issueprobe.com/docs/agents/ and the machine-readable
contract is https://issueprobe.com/openapi.json. This document is the order of operations and the
places to stop; those two are the authority on field names and response shapes. Where they disagree
with this page, believe them.

## Before you start: two hard stops

**Step 3 needs a human.** You cannot enroll yourself. A person has to open an email and approve.
Plan to pause there and tell your user clearly what you need from them.

**Step 7 and step 12 change something outside your control.** Adding a deploy key changes the user's
repository settings, and starting Checkout begins a US$49/month subscription. Do neither without
explicit authority from your user.

Ask the owner, through whatever trusted channel started this work, for:

* the **owner email address** for the account
* the **exact website origin**, for example `https://www.example.com`
* the **SSH repository location** and default branch
* confirmation that they can **add a deploy key** to that repository

## 1. Start enrollment with narrow scopes

Request only the scopes the work actually needs. `credentials:write` is not an enrollment scope: an
agent can never create or revoke credentials.

```
curl --fail-with-body https://issueprobe.com/v1/agent-enrollments \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: setup-owner-example-001' \
  -H 'X-IssueProbe-Client: your-agent-name' \
  --data '{
    "ownerEmail": "owner@example.com",
    "accountName": "Example account",
    "agentName": "website-setup-agent",
    "scopes": ["websites:write", "websites:read", "reports:read"]
  }'
```

Returns 201:

```
{
  "enrollmentId": "enr_...",
  "exchangeSecret": "ip_enroll_exchange_...",
  "expiresAt": "...",
  "replayed": false
}
```

The response is deliberately identical whether or not that email already has an account. Do not read
anything into it.

Available scopes: `websites:write`, `websites:read`, `reports:read`, `reports:write`,
`audit:read`, `billing:checkout`. Ask for the smallest set that does the job.

## 2. Store the exchange secret safely — check the destination FIRST

`exchangeSecret` is returned once. It cannot be recovered later, and it is the only thing that turns
the owner's approval into a usable credential.

**Before writing it anywhere, verify the destination is not version controlled.** Check:

```
git -C <target-directory> rev-parse --is-inside-work-tree 2>/dev/null
```

If that prints `true`, pick somewhere else, or confirm the file is ignored and that the ignore rule
is itself committed.

Prefer, in order:

1. A secret manager the project already uses.
2. An environment variable, set outside the repository.
3. A file outside the working tree with restrictive permissions.

Never: a tracked config file, a directory that syncs to cloud storage, source code, a commit
message, a URL, an issue, or a log line. Do not echo it back into a chat transcript that gets
stored.

An exact retry — same client identifier, same idempotency key, same body — returns the same unused
secret while the enrollment is still pending or approved. A *changed* retry fails. That is a
recovery path for a dropped response, not a way to get a second secret.

## 3. STOP. Wait for the owner to approve by email

IssueProbe emails the owner one authorization link. Opening the link does **not** approve anything:
the owner sees the account name, the agent name and the exact scopes you requested, then approves or
rejects through an explicit form.

Tell your user, in plain words, that you are blocked:

> I have requested access to IssueProbe for owner@example.com. Please open the email from IssueProbe
> and approve the request for "website-setup-agent". It lists the exact permissions. Tell me once
> you have approved and I will continue.

While you wait:

* **Do not** send more enrollment requests. Repeats are rate limited per email, per client and
  globally, and they will not make the email arrive faster.
* **Do not** poll for approval in a tight loop.
* **Do not** invent an alternative path. There isn't one.

The enrollment and its secret expire after seven days. If that passes, start again from step 1.

## 4. Exchange the secret exactly once

After the owner confirms approval:

```
curl --fail-with-body https://issueprobe.com/v1/agent-enrollments/ENROLLMENT_ID/exchange \
  -H 'Content-Type: application/json' \
  --data '{"exchangeSecret":"ip_enroll_exchange_..."}'
```

This returns one scoped API credential, once. **Store it with the same care as step 2.** A second
exchange fails and does not produce another credential — if you lose it, the owner has to approve a
fresh enrollment. The issued credential expires 90 days after exchange.

Every call from here uses it as a bearer token:

```
-H 'Authorization: Bearer YOUR_API_CREDENTIAL'
```

## 5. Register the website and its repository

```
curl --fail-with-body https://issueprobe.com/v1/websites \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Content-Type: application/json' \
  --data '{
    "name": "Example Site",
    "primaryOrigin": "https://www.example.com",
    "repository": {
      "sshUrl": "git@example.com:owner/site.git",
      "defaultBranch": "main"
    }
  }'
```

The response contains the public widget key and the **public** deploy key. It never contains a
private key. Omit `deployedRevision` for branch-head mode; supply a hexadecimal revision only if the
owner wants a pinned revision.

`position` optionally places the widget launcher: `bottom-right` (default), `bottom-left`,
`top-right` or `top-left`.

## 6. Prove you control the origin

Read the website detail. Each pending origin carries an exact HTTPS verification URL and a
64-character public text value.

```
curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL'
```

Publish the returned `verification.content` as the complete body at `verification.url`. It is public
placement content, not a credential. Then:

```
curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/origins/ORIGIN_ID/verify \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Content-Type: application/json' \
  --data '{"method":"well_known"}'
```

A redirect, an HTTP origin, a path, a query value, a fragment, or different file content all fail
verification. The file may end with a single newline.

## 7. PAUSE. Add the public deploy key to the repository

This changes the user's repository settings, so confirm before doing it, or hand the key over and
let them do it.

Add the returned public key to the registered repository with **read-only** access. Use it for this
website only and do not grant write authority. IssueProbe never pushes, never creates a release, and
never runs repository scripts.

## 8. Check readiness

```
curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/repository/check \
  -X POST -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Content-Type: application/json' --data '{}'

curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/readiness \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL'
```

Continue only when the origin, repository and widget checks all report ready. In branch-head mode,
re-run the repository check after each deployment so the recorded revision matches what is live.

## 9. Install the widget

```
curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/install \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL'
```

Place the returned script tag on the verified website. The public widget key it carries grants no
account or repository access, and it only works from a verified origin.

Evidence categories can be narrowed with `PUT /v1/websites/WEBSITE_ID/evidence-settings`. A category
can be switched off. No setting can switch on cookie, form or local-storage **values** — those are
never available.

## 10. Submit one safe test report

Use content with no private values in it. `contactEmail` is optional. When you include it, use an
address the reporter authorized for this test. IssueProbe stores it for the owner, never sends to it,
and never gives it to the investigation.

```
curl --fail-with-body 'https://issueprobe.com/v1/public/reports?websiteKey=PUBLIC_WIDGET_KEY' \
  -H 'Origin: https://www.example.com' \
  -H 'Idempotency-Key: safe-test-report-001' \
  -H 'X-IssueProbe-Client: your-agent-name' \
  -F 'payload=<test-report.json'
```

A 201 with `"investigation": {"state": "queued"}` means it was accepted for investigation. A 201 with
`"investigation": null` and `"quota": {"code": "monthly_investigation_limit_reached"}` means the
report was stored but the monthly allowance is used up — that is a successful store, not an error.

## 11. Confirm the result reaches review

```
curl --fail-with-body 'https://issueprobe.com/v1/reports?limit=25' \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL'

curl --fail-with-body https://issueprobe.com/v1/reports/REPORT_ID \
  -H 'Authorization: Bearer YOUR_API_CREDENTIAL'
```

An investigation takes time; the report moves through `queued` and `investigating` before
`ready_for_review`. The result may legitimately conclude `unknown`. That is a real answer, not a
failure — report it as one.

The draft response is **not** sent to anyone. A human reviews it and decides.

## 12. PAUSE before upgrading

Only if the owner explicitly asks, and only with `billing:checkout`:

```
curl --fail-with-body https://issueprobe.com/v1/billing/checkout \
  -X POST -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Idempotency-Key: owner-pro-upgrade-001' \
  -H 'Content-Type: application/json' --data '{}'
```

Give the hosted URL to the owner. Never collect card data. Returning from Checkout does not by
itself grant Pro — confirm the plan afterwards rather than assuming.

## Optional: report notifications

Only if the owner asks for it. IssueProbe can send one signed HTTPS POST to one owner endpoint when
a visitor sends a report. This needs `websites:write` (and `websites:read` to read it back).

```
curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/webhook \
  -X PUT -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Content-Type: application/json' \
  --data '{"url":"https://hooks.example.com/issueprobe","enabled":true}'

curl --fail-with-body https://issueprobe.com/v1/websites/WEBSITE_ID/webhook/test \
  -X POST -H 'Authorization: Bearer YOUR_API_CREDENTIAL' \
  -H 'Content-Type: application/json' --data '{}'
```

The first PUT returns 201 with `signingSecret` (`whsec_...`) exactly once. Store it in the
owner's secret store before you do anything else. The URL must be public HTTPS on port 443; a query
string is allowed. The event body holds identifiers and links only — never the report text, the
contact email, evidence, or a result. Each request carries Standard Webhooks `webhook-id`,
`webhook-timestamp`, and `webhook-signature` headers; verify the raw body with the secret.
Verification steps: https://issueprobe.com/docs/agents/#report-notifications

## Errors

Every error carries a stable code and a safe message:

```
{"error": {"code": "origin_not_allowed", "message": "...", "requestId": "req_..."}}
```

Surface the code and message to your user rather than paraphrasing them.

* **400** — the request shape is wrong. Fix it; do not add undocumented fields.
* **401** — stop. The credential, claim or exchange value is invalid or expired.
* **403** — stop. The account, scope, origin or owner authority does not permit this.
* **409** — read the code. `website_limit_reached` means the plan's website allowance is used.
  Do not retry a changed request under an old idempotency key.
* **410** — the enrollment or artifact expired. Ask the owner for a new approved enrollment.
* **429** — a public rate limit. Wait. Do not rotate client identifiers to evade it.

Keep the `requestId` for support. Never log credentials, exchange secrets, owner authorization
values, or report evidence.

## Verification checklist

Prove the integration end to end before reporting success:

1. The owner approved the enrollment and you exchanged the secret exactly once.
2. The credential is stored outside version control, and you have confirmed that.
3. `GET /v1/websites/WEBSITE_ID/readiness` reports origin, repository and widget all ready.
4. The widget script is live on the verified origin.
5. A test report returns 201 and appears in `GET /v1/reports`.
6. The report reaches `ready_for_review`, or you can explain why it did not.
7. You have told the owner that every draft waits for their review and that IssueProbe never
   messages a visitor.

Full reference: https://issueprobe.com/docs/agents/ and https://issueprobe.com/openapi.json
