# 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/…`)
| `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)
| `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/