Reciprocity shutter speed calculator for aperture stop changes
Changing aperture alters how much light reaches the sensor or film, so an equivalent exposure needs a compensating shutter-speed change.
Run — free
This reciprocity shutter speed calculator starts with the original exposure time and an aperture change measured in stops, then returns the new time required to preserve exposure. Positive stops mean closing the aperture and therefore lengthening the exposure; negative stops mean opening it and shortening the exposure. The result includes seconds, a readable shutter-speed label, and the exact exposure multiplier used in the calculation.
Describe the starting exposure and aperture change
Enter the original shutter speed as a duration in seconds. A camera setting written as 1/125 second is 0.008 seconds, while a two-second exposure is simply 2. Then enter the aperture movement in stops. Use a positive value when closing the aperture, because less light passes through the lens and the shutter must remain open longer. Use a negative value when opening the aperture, because more light reaches the image plane and the exposure time must become shorter. Whole stops are common, but the calculator also accepts fractional changes such as 0.3, 0.5, or 1.5 stops. The sign convention is important: moving from f/4 to f/8 closes the aperture by two stops, so enter 2. Moving from f/8 to f/4 opens it by two stops, so enter -2. This explicit convention avoids relying on f-number interpretation and also works when the stop difference was obtained from a meter, filter calculation, or lens transmission test. Inputs must be finite numbers within the published ranges.
Understand the reciprocity calculation
Each full stop represents a factor of two in exposure. The calculator raises two to the aperture-change value and multiplies the original shutter duration by that factor. Closing by one stop doubles the required time; closing by two stops multiplies it by four. Opening by one stop halves the time because two raised to minus one equals one half. Fractional stops follow the same exponential relationship, so half a stop uses the square root of two rather than a rough linear adjustment. The response reports the original duration, signed stop change, exposure multiplier, new duration in seconds, and a formatted shutter-speed label. Numeric results are rounded to a stable precision so identical inputs produce identical JSON across environments. The readable label is a convenience: durations below one second are shown as a reciprocal when practical, while longer durations remain in seconds. Use the numeric seconds field for downstream calculations, camera-control software, or comparisons, since actual camera dials may offer only standardized increments and may require choosing the nearest available setting.
Apply the result without changing the exposure target
The computed time preserves the mathematical exposure implied by the starting settings. It is useful when changing depth of field, moving between lens apertures, or planning a sequence in which aperture changes but brightness should remain consistent. It does not account for every physical effect. Very long film exposures can exhibit reciprocity failure, and some film manufacturers recommend additional time or color correction beyond the ordinary stop relationship. Digital sensors may introduce thermal noise during long exposures, while neutral-density filters can have real transmission values that differ slightly from their labels. Camera shutter controls also quantize times into full-, half-, or third-stop steps. Treat the returned value as the exact reciprocal target, then select the closest supported shutter speed or use a timed bulb exposure when precision matters. If another variable changes at the same time, such as ISO or filter density, combine all changes into a net stop adjustment before calculating. The endpoint is deterministic and performs no network requests, so it can also serve as a reliable building block in exposure-planning tools, shot lists, and repeatable testing workflows.
What you can do with it
Preserve brightness while changing depth of field
Find the compensating shutter time after closing or opening the lens for a different depth of field.
Plan manual exposure brackets
Calculate exact time changes for whole or fractional aperture-stop adjustments in a repeatable shot sequence.
Automate exposure worksheets
Feed numeric seconds into a shot-planning tool while retaining the multiplier and readable setting for review.
FAQ
What does a positive aperture change mean?
It means closing the aperture by that many stops. The calculator lengthens the shutter time to compensate.
How do I enter 1/125 second?
Enter 0.008 in shutter_speed_seconds, because one divided by 125 equals 0.008 seconds.
Can I use fractional stops?
Yes. Fractional values use the exact exponential stop relationship, including half- and third-stop changes.
Does this correct film reciprocity failure?
No. It calculates ordinary exposure reciprocity. Apply the film manufacturer's long-exposure correction separately when required.
Why might my camera not show the exact result?
Many cameras offer standardized full-, half-, or third-stop shutter settings. Choose the nearest available setting or use timed bulb mode.
What does the API request cost?
Each API request costs $0.002. The browser calculator can run the same deterministic calculation locally.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/optics/reciprocity-shutter-speed \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"shutter_speed_seconds":0.008,"aperture_change_stops":2}'const res = await fetch("https://api.kit.forhosting.com/optics/reciprocity-shutter-speed", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"shutter_speed_seconds": 0.008,
"aperture_change_stops": 2
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/optics/reciprocity-shutter-speed",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"shutter_speed_seconds": 0.008,
"aperture_change_stops": 2
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/optics/reciprocity-shutter-speed", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"shutter_speed_seconds":0.008,"aperture_change_stops":2}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"shutter_speed_seconds":0.008,"aperture_change_stops":2}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/optics/reciprocity-shutter-speed", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"shutter_speed_seconds": 0.008,
"aperture_change_stops": 2
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "optics.reciprocity_shutter_speed",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |