hackerone-api 0.2.0

Unofficial, dependency-light Rust client for the HackerOne API (v1): submit reports, read your reports, hacktivity, balance, and earnings.
Documentation
# hackerone-api

Unofficial, dependency-light Rust client for the [HackerOne API][api] (v1).

Blocking, no async runtime. HTTP Basic auth. An injectable transport so tests
use a mock and embedders can swap the HTTP stack.

It covers both the **hacker** surface (submit reports, your reports,
hacktivity, balance, earnings) and the **customer** read surface (programs,
scopes, reports, weaknesses).

```rust
use hackerone_api::{Client, CreateHackerReport, SeverityRating};

fn main() -> Result<(), hackerone_api::Error> {
    let client = Client::new("api-identifier", "api-token");

    // Submit a report as a hacker (POST /v1/hackers/reports).
    let report = CreateHackerReport::new("chia_network", "Remote panic in ProofOfSpace::parse")
        .vulnerability_information("## Steps\n1. …")
        .impact("Any node on the gossip path aborts.")
        .severity(SeverityRating::High)
        .weakness_id(1337)
        .structured_scope_id(57);

    let created = client.create_report(&report)?;
    println!("filed: {:?}", created.title);
    Ok(())
}
```

## Auth

HTTP Basic: the **username** is your API token *identifier* and the
**password** is the token *value*. Create one in your HackerOne account
settings. The examples read:

```sh
export HACKERONE_API_IDENTIFIER="..."
export HACKERONE_API_TOKEN="..."
```

Never commit a token. In AXIOM, seed it in the vault
(`ax-vault put persona/smurf77/hackerone_api_token hackerone-api`) and inject it
at call time.

## Endpoints

### Hacker surface (`/v1/hackers/…`)

| Method | Endpoint |
|---|---|
| `create_report(&CreateHackerReport)` | `POST /v1/hackers/reports` |
| `my_reports(&PageQuery)` | `GET /v1/hackers/me/reports` |
| `my_report(id)` | `GET /v1/hackers/reports/{id}` |
| `hacktivity(&HacktivityQuery)` | `GET /v1/hackers/hacktivity` |
| `balance()` | `GET /v1/hackers/payments/balance` |
| `earnings(&PageQuery)` | `GET /v1/hackers/payments/earnings` |

### Customer read surface (kept)

| Method | Endpoint |
|---|---|
| `me()` | `GET /v1/me` |
| `programs()` | `GET /v1/me/programs` |
| `program(id)` | `GET /v1/programs/{id}` |
| `structured_scopes(program, page)` | `GET /v1/programs/{id}/structured_scopes` |
| `reports(&ReportQuery)` | `GET /v1/reports` |
| `report(id)` | `GET /v1/reports/{id}` |
| `add_comment(report, text)` | `POST /v1/reports/{id}/activities` |
| `change_state(report, state, msg)` | `POST /v1/reports/{id}/state_changes` |
| `weaknesses()` | `GET /v1/weaknesses` |
| `next_page(&page)` | follows a `links.next` URL |
| `get_raw(path, query)` | any authenticated GET |

## Submitting a report

Report submission is a **hacker** operation and must go to
`POST /v1/hackers/reports` with a `team_handle` attribute — not the customer
`/v1/reports` surface. The body this crate sends is exactly:

```json
{
  "data": {
    "type": "report",
    "attributes": {
      "team_handle": "chia_network",
      "title": "…",
      "vulnerability_information": "…",
      "impact": "…",
      "severity_rating": "high",
      "weakness_id": 1337,
      "structured_scope_id": 57
    }
  }
}
```

`team_handle`, `title`, `vulnerability_information`, and `impact` are required;
`severity_rating` (one of `none|low|medium|high|critical`), `weakness_id`
(integer), and `structured_scope_id` (integer) are optional and omitted when
unset. The response is the created `report` resource.

Try it without submitting:

```sh
cargo run --example submit_report -- chia_network "Title" poc.md "Impact" high 1337 57
# add H1_API_SUBMIT=1 to actually POST
```

## Errors

Failures are typed. A non-2xx response becomes `Error::Api` carrying the HTTP
status and the server's parsed `errors` array:

```rust
if let Err(e) = client.create_report(&report) {
    if e.is_client_error() {
        eprintln!("HTTP {}: {}", e.status().unwrap_or(0), e.detail().unwrap_or(""));
        for api_error in e.api_errors() {
            eprintln!("  - {:?}: {:?}", api_error.title, api_error.detail);
        }
    }
}
```

## Design

- `#![forbid(unsafe_code)]`, `#![warn(missing_docs)]`.
- Errors are typed: `Error::{Transport, Decode, Invalid, Api}`.
- Transport is a trait (`Transport`); `UreqTransport` is the default.
- Domain types keep the documented fields and stash unknown ones in a
  flattened `extra` map (and JSON:API ids accept string *or* integer).
- Tests run entirely against a mock transport — no network.

## Status

`0.2.0` — hacker report submission corrected to `POST /v1/hackers/reports`;
hacker report list/get, hacktivity, balance, and earnings added.

## License

MIT. See [LICENSE](LICENSE).

## Disclaimer

This crate is unofficial and not affiliated with or endorsed by HackerOne.
"HackerOne" is a trademark of its owner; the name is used only to describe
what the library talks to.

[api]: https://api.hackerone.com/