# RC Servo & PWM API
> RC servo and PWM maths as an API, computed locally and deterministically — the pulse-width, angle and duty-cycle numbers a robotics, RC or embedded developer drives a servo with. The angle endpoint turns a pulse width into the servo angle: a hobby servo reads the width of the pulse (not a duty cycle), so the standard 1000–2000 µs maps linearly across the travel with 1500 µs at centre — angle = (pulse − min) ÷ the min-to-max span × the travel — and it flags when a pulse asks for more than the configured range so you do not drive the servo into its mechanical stops. The pulse endpoint runs it the other way, giving the pulse width a microcontroller should write for a target angle (90° is 1500 µs on a 1000–2000 µs / 180° servo), exactly what an Arduino-style servo library computes under the hood. The duty endpoint converts a pulse and a refresh frequency into the PWM period and duty cycle: a 50 Hz servo frame is 20 ms, so a 1500 µs pulse is just 7.5 % duty — the value a timer peripheral needs — and faster frames for digital servos or multirotor ESCs (e.g. 333 Hz) change it. Everything is computed locally and deterministically, so it is instant and private. Ideal for robotics and RC firmware, microcontroller and embedded tools, drone and animatronics projects, and maker calculators. Pure local computation — no key, no third-party service, instant. 3 compute endpoints. For stepper steps-per-mm use a stepper-motor API.

## Authentication
All requests require your oanor API key in the `x-oanor-key` header. Get one at https://www.oanor.com/developer/keys.

```bash
curl -H "x-oanor-key: oanor_live_…" "https://api.oanor.com/servo-api/..."
```

## Pricing
- **Free** (Free) — 8,200 calls/Mo, 2 req/s
- **Starter** ($8/Mo) — 84,000 calls/Mo, 6 req/s
- **Pro** ($28/Mo) — 345,000 calls/Mo, 15 req/s
- **Mega** ($85/Mo) — 1,580,000 calls/Mo, 40 req/s

## Endpoints

### Servo

#### `GET /v1/angle` — Servo angle from a pulse width

**Parameters:**
- `pulse_us` (query, required, string) — Pulse width (µs) Example: `1500`
- `min_us` (query, optional, string) — Min pulse (µs, default 1000) Example: `1000`
- `max_us` (query, optional, string) — Max pulse (µs, default 2000) Example: `2000`
- `range_deg` (query, optional, string) — Travel (degrees, default 180) Example: `180`

**Example:**
```bash
curl -H "x-oanor-key: $KEY" \
  "https://api.oanor.com/servo-api/v1/angle?pulse_us=1500&min_us=1000&max_us=2000&range_deg=180"
```

**Response:**
```json
{
    "data": {
        "note": "A hobby RC servo reads the width of the pulse, not a PWM duty cycle: the standard 1000–2000 µs maps linearly across the travel, with 1500 µs at centre. angle = (pulse − min) ÷ (min-to-max span) × the travel. Many servos accept a wider 500–2500 µs for extra throw, but pushing past the mechanical stops strains the gears — the unclamped value here shows when a pulse asks for more than the configured range.",
        "inputs": {
            "max_us": 2000,
            "min_us": 1000,
            "pulse_us": 1500,
            "range_deg": 180
        },
        "angle_deg": 90,
        "out_of_range": false,
        "angle_unclamped_deg": 90
    },
    "meta": {
        "timestamp": "2026-06-06T23:53:51.218Z",
        "request_id": "2cd4666b-66c4-47f0-bafd-ad4e6ac3282e"
    },
    "status": "ok",
    "message": "Servo angle",
    "success": true
}
```

#### `GET /v1/duty` — PWM period and duty cycle

**Parameters:**
- `pulse_us` (query, required, string) — Pulse width (µs) Example: `1500`
- `frequency_hz` (query, optional, string) — Refresh frequency (Hz, default 50) Example: `50`

**Example:**
```bash
curl -H "x-oanor-key: $KEY" \
  "https://api.oanor.com/servo-api/v1/duty?pulse_us=1500&frequency_hz=50"
```

**Response:**
```json
{
    "data": {
        "note": "The refresh frequency sets the frame: a standard 50 Hz servo signal has a 20 ms (20,000 µs) period, so a 1500 µs pulse is just 7.5 % duty cycle. The servo only cares about the pulse width, but the controller's PWM peripheral is configured in duty, so this conversion sets the timer. Digital servos and multirotor ESCs often run faster frames (e.g. 333 Hz) for crisper response — drop the frequency in to see the new duty.",
        "inputs": {
            "pulse_us": 1500,
            "frequency_hz": 50
        },
        "period_ms": 20,
        "period_us": 20000,
        "off_time_us": 18500,
        "duty_cycle_pct": 7.5
    },
    "meta": {
        "timestamp": "2026-06-06T23:53:51.321Z",
        "request_id": "b29dd58a-576a-43a7-a81d-c7607e651ce2"
    },
    "status": "ok",
    "message": "PWM duty",
    "success": true
}
```

#### `GET /v1/pulse` — Pulse width for a target angle

**Parameters:**
- `angle_deg` (query, required, string) — Angle (degrees) Example: `90`
- `min_us` (query, optional, string) — Min pulse (µs, default 1000) Example: `1000`
- `max_us` (query, optional, string) — Max pulse (µs, default 2000) Example: `2000`
- `range_deg` (query, optional, string) — Travel (degrees, default 180) Example: `180`

**Example:**
```bash
curl -H "x-oanor-key: $KEY" \
  "https://api.oanor.com/servo-api/v1/pulse?angle_deg=90&min_us=1000&max_us=2000&range_deg=180"
```

**Response:**
```json
{
    "data": {
        "note": "The pulse width for an angle inverts the servo map: pulse = min + (angle ÷ travel) × the min-to-max span, so on a 1000–2000 µs / 180° servo, 90° is 1500 µs and 45° is 1250 µs. This is the number a microcontroller writes to the servo each frame (about every 20 ms); libraries like Arduino's Servo take the angle and do exactly this conversion under the hood.",
        "inputs": {
            "max_us": 2000,
            "min_us": 1000,
            "angle_deg": 90,
            "range_deg": 180
        },
        "pulse_us": 1500
    },
    "meta": {
        "timestamp": "2026-06-06T23:53:51.426Z",
        "request_id": "5d1b7465-abbe-47c5-9dfb-058d473df9c1"
    },
    "status": "ok",
    "message": "Servo pulse",
    "success": true
}
```

### Meta

#### `GET /v1/meta` — Spec

**Example:**
```bash
curl -H "x-oanor-key: $KEY" \
  "https://api.oanor.com/servo-api/v1/meta"
```

**Response:**
```json
{
    "data": {
        "notes": "µs, degrees, Hz, %. angle = (pulse−min)/(max−min)×travel; pulse = min+(angle/travel)×span; duty = pulse/period, period = 1e6/freq. RC servos are pulse-width, not duty, devices. For stepper steps/mm use a stepper-motor API.",
        "service": "servo-api",
        "endpoints": {
            "GET /v1/duty": "PWM period and duty cycle from a pulse and refresh frequency.",
            "GET /v1/meta": "This document.",
            "GET /v1/angle": "Servo angle from a pulse width (and min/max/travel).",
            "GET /v1/pulse": "Pulse width for a target angle."
        },
        "description": "RC servo / PWM maths: servo angle from a pulse width, pulse width for an angle, and PWM period and duty cycle."
    },
    "meta": {
        "timestamp": "2026-06-06T23:53:51.519Z",
        "request_id": "e090cfdd-debf-4225-bd7a-3e4b3bc5436c"
    },
    "status": "ok",
    "message": "Meta",
    "success": true
}
```


---
Marketplace page: https://www.oanor.com/api/servo-api
OpenAPI spec: https://www.oanor.com/api/servo-api/openapi.json
