# Octrahire public API v1: endpoints, auth and scopes

> Octrahire's public REST API v1, in beta: base URL, API keys and OAuth tokens, scopes, errors, pagination, idempotency, rate limits and every endpoint.

Source: https://octrahire.ai/developers/api

A REST API for roles, applications, interviews, scorecards, offers and webhooks. It is in beta: these paths and names are fixed, and anything we add will not break them.

## Status

- **Public API v1** (In beta): The endpoints below.
- **API keys with scopes** (In beta): Made by a workspace owner, each with its own scopes.
- **OAuth access tokens** (In beta): For partner apps, acting as the person who signed in.
- **OpenAPI 3.1 specification** (In beta): At /openapi.json under the base URL.

Available: live in production today. In beta: deployed and usable today; it may still change. Coming soon: not usable yet.

## Basics

- Base URL: https://api.octrahire.ai/api/v1/public
- JSON in and out. Times are ISO 8601 in UTC
- Lists take ?limit= (default 25, up to 100) and ?cursor=, and return {"data": [...], "next_cursor": ...}
- Writes accept an Idempotency-Key header; the first response is replayed for 24 hours
- Rate limits are per credential. Over the limit you get 429 with Retry-After
- OpenAPI 3.1 specification: https://api.octrahire.ai/api/v1/public/openapi.json

## Authentication

Send a credential as a bearer token. API keys start with ohk_ and OAuth access tokens with oat_. An API key can also go in an X-Api-Key header.

Who am I?:

```
curl https://api.octrahire.ai/api/v1/public/me \
  -H "Authorization: Bearer ohk_your_key"
```

/me returns the workspace, the seat the credential acts as, and its scopes.

## Scopes

A key or grant has only the scopes chosen for it, and never more than the seat that made it.

| Scope | Allows |
|---|---|
| `roles:read` | See your roles and their pipelines |
| `roles:write` | Create and edit roles |
| `candidates:read` | See candidates and applications |
| `applications:write` | Add candidates and move them between stages |
| `interviews:read` | See AI interview results, reports and transcripts |
| `interviews:write` | Invite candidates to interviews and schedule them |
| `scorecards:read` | See scorecards and notes |
| `scorecards:write` | Submit scorecards and add notes |
| `offers:read` | See offers |
| `offers:write` | Draft offers and send them for approval |
| `approvals:write` | Approve or decline requisitions and offers (owner only) |
| `ask` | Ask questions across interviews |
| `reports:read` | See hiring and Emiratisation reports |
| `webhooks:manage` | Manage webhook endpoints (owner only) |

## Endpoints

| Endpoint | What it does | Scope |
|---|---|---|
| `GET /me` | The workspace, seat and scopes | `any` |
| `GET /roles` | List roles; ?status=open, closed or draft | `roles:read` |
| `GET /roles/{role_id}` | One role | `roles:read` |
| `POST /roles` | Create a role: title, description, location, language, interview preset | `roles:write` |
| `PATCH /roles/{role_id}` | Edit a role | `roles:write` |
| `POST /roles/{role_id}/open` | Open a role (also /close) | `roles:write` |
| `GET /roles/{role_id}/pipeline` | Counts per stage | `roles:read` |
| `GET /applications` | List applications; ?role_id=, ?stage=, ?updated_since= | `candidates:read` |
| `GET /applications/{id}` | One application | `candidates:read` |
| `POST /applications` | Add a candidate to a role | `applications:write` |
| `POST /applications/{id}/move` | Move to a stage, with a reason | `applications:write` |
| `POST /applications/{id}/reject` | Reject, with a reason category and note | `applications:write` |
| `POST /applications/{id}/interview-invites` | Invite to an AI interview by email or WhatsApp | `interviews:write` |
| `GET /applications/{id}/interview` | Report summary, scores and evidence | `interviews:read` |
| `GET /applications/{id}/transcript` | The interview transcript | `interviews:read` |
| `POST /applications/{id}/schedule` | Schedule a human interview | `interviews:write` |
| `GET /applications/{id}/scorecards` | Scorecards | `scorecards:read` |
| `POST /applications/{id}/scorecards` | Submit a scorecard as your seat | `scorecards:write` |
| `GET /applications/{id}/notes` | Notes | `scorecards:read` |
| `POST /applications/{id}/notes` | Add a note | `scorecards:write` |
| `GET /offers` | List offers; ?application_id= | `offers:read` |
| `GET /offers/{offer_id}` | One offer | `offers:read` |
| `POST /offers` | Draft an offer | `offers:write` |
| `POST /offers/{offer_id}/submit` | Send a draft for approval | `offers:write` |
| `POST /offers/{offer_id}/send` | Send an approved offer to the candidate | `offers:write` |
| `GET /approvals` | Approvals waiting for your seat | `approvals:write` |
| `POST /approvals/{approval_id}` | Approve or decline | `approvals:write` |
| `GET /webhooks` | List webhooks | `webhooks:manage` |
| `POST /webhooks` | Add a webhook; the secret is shown once | `webhooks:manage` |
| `DELETE /webhooks/{webhook_id}` | Remove a webhook | `webhooks:manage` |
| `POST /webhooks/{webhook_id}/rotate-secret` | Rotate a webhook's secret | `webhooks:manage` |
| `GET /events` | The event catalogue | `any` |

## Add a candidate

Request:

```
curl -X POST https://api.octrahire.ai/api/v1/public/applications \
  -H "Authorization: Bearer ohk_your_key" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2a90-applicant-4471" \
  -d '{"role_id": "rol_123", "source": "careers-site",
       "candidate": {"name": "Mariam Al Hashimi", "email": "mariam@example.com"}}'
```

## Errors

Every error has the same shape, with the HTTP status:

```
{"error": {"code": "insufficient_scope", "message": "This key needs applications:write"}}
```

Codes: unauthorized, insufficient_scope, forbidden, not_found, validation_error, conflict, rate_limited, idempotency_conflict.

## Blind review

When a role hides candidate identity, name, email, phone and photo come back as null and anonymous_label is set, for every credential, including the owner's.

[Webhooks](https://octrahire.ai/developers/webhooks)
