Send one safety data sheet as a PDF and receive its fields as JSON.
Base URL: https://api.safetydatasheetapi.com. Replace YOUR_KEY with the API key you were given.
curl -sS -X POST "https://api.safetydatasheetapi.com/v1/extract" \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @sheet.pdf \
-o sheet.json -w "%{http_code}\n"
$r = Invoke-WebRequest -Uri "https://api.safetydatasheetapi.com/v1/extract" -Method Post `
-Headers @{ Authorization = "Bearer YOUR_KEY" } `
-ContentType "application/pdf" -InFile "sheet.pdf" -SkipHttpErrorCheck
$r.StatusCode
$r.Content | Set-Content -Encoding utf8 sheet.json
import requests
with open("sheet.pdf", "rb") as f:
r = requests.post(
"https://api.safetydatasheetapi.com/v1/extract",
headers={"Authorization": "Bearer YOUR_KEY", "Content-Type": "application/pdf"},
data=f,
timeout=300,
)
print(r.status_code, r.headers.get("schema-version"))
data = r.json()
A 200 means every field was filled and every built-in check passed. Anything else is an error with a JSON body; see Errors.
Send your key on every request as Authorization: Bearer <key>. Keys start with sds_. Keep the key out of source control and client-side code; if it leaks, ask us to revoke it and we will issue a new one.
The request body is the PDF itself (not a multipart form), with Content-Type: application/pdf. The response is the extracted fields as JSON, following your schema (GET /v1/schema). Optional query parameter: envelope=true (see Evidence envelope).
The JSON Schema (draft 2020-12) that your successful responses follow. Needs your key; does not count against your allowance.
Service check. No key needed.
This API described in OpenAPI 3.1, for code generators and API tools. No key needed.
The fields returned, and their format, are the ones agreed with you. Paste your key to list them here. The key is sent only to https://api.safetydatasheetapi.com/v1/schema and is not stored by this page.
Every field is always present in a 200 response: a field the sheet does not contain is null (or [] for lists).
By default the response body is exactly the field JSON. With ?envelope=true it becomes {"data": …, "meta": …}, where meta holds the request id, the schema id, the page count, the model calls made, every check that was run with its result, and the source rows each field was read from. With envelope=true, a 422 also carries the partial output under partial, so you can see what was found.
Errors are JSON: {"error": "<code>", "detail": "<explanation>", "request_id": "<id>"}. Please quote the request_id (also in the x-request-id header) when you contact us.
| Status | Error code | Meaning |
|---|---|---|
| 400 | not_a_pdf | The body is not a PDF. |
| 401 | missing_api_key, unknown_key | No key, or the key is not valid. |
| 403 | revoked, expired, allowance_used | The key exists but cannot be used. |
| 405 | method_not_allowed | Wrong HTTP method. |
| 413 | too_large, too_many_pages | The PDF is over the size or page limit. |
| 415 | unsupported_media_type | The Content-Type is not application/pdf. |
| 422 | unreadable_pdf, extraction_incomplete | The PDF has no readable text layer, or the extraction is incomplete (a check failed or a field could not be filled). The response lists failed_checks and pending fields. |
| 500 | internal_error | Unexpected error. |
| 502 | model_error | The model stage failed. Not counted against your allowance; retry later. |
| 503 | model_unavailable, budget_exhausted | The service cannot take documents right now. |
Retrying makes sense for 502 and 503, after a pause. A 422 for the same PDF will usually repeat: send us the request_id instead.
422 unreadable_pdf.200 responses count against your key's document allowance. Errors, including 422 and 502, are not counted.403./v1 path prefix. It changes only for a breaking change to requests, authentication or error format. New endpoints or optional parameters do not change it.schema-version header on every response to a request with your key. A changed field list gets a new id, and we agree the switch with you in advance.engine-version header of every /v1 response. Useful when you report a difference between two runs.