Overview
The EngineeringID REST API provides a single, free endpoint for generating professional stamp images. No authentication required.
POST /v1/stamp
Generates a professional stamp image (seal graphic) for any licensed professional. No authentication required.
/v1/stamp
- Base URL
https://api.engineeringid.com- Content-Type
-
application/jsonapplication/x-www-form-urlencoded - Returns
-
image/svg+xml·image/png·application/pdf
Request
Body
{
"name": "Jane Smith",
"license": "PE-12345",
"state": "california",
"profession": "engineer",
"format": "svg",
"discipline": "CIVIL",
"city": "Los Angeles",
"date": "2026-05-08"
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name |
String | Yes | Full name of the professional (e.g., "Jane Smith"). Must not be empty. |
license |
String | Yes | Professional license or credential number (e.g., "PE-12345"). Format varies by profession and state. |
state |
String | Yes | State name or abbreviation (e.g., "california", "CA", "new_york"). Case-insensitive. |
profession |
String | No | Profession type. Defaults to "engineer". Supported: engineer (and subtypes like civil-engineer, structural-engineer, etc.), architect, land-surveyor, landscape-architect. |
format |
String | No | Output format. Defaults to "png". Options: "svg" (vector), "png" (raster, 4× scale), "pdf" (print-ready, 8× scale). |
discipline |
String | No | Engineering discipline (e.g., "CIVIL", "STRUCTURAL"). Only applicable to stamps that include a discipline field. |
city |
String | No | City name. Only applicable to stamps that include a city field (e.g., some architect stamps). |
state_label |
String | No | State abbreviation label on the stamp (e.g., "WIS."). Only for stamps with a separate state text field. |
date |
String | No | Date string for stamps that include a date field (e.g., "01/15/26"). |
font_family |
String | No | CSS font-family stack. Default: "Helvetica, Liberation Sans, sans-serif". Allowed characters: letters, digits, spaces, commas, single quotes, hyphens, periods. Max 200 chars. |
font_weight |
String | No | Default: "normal". Allowed: normal, bold, bolder, lighter, 100, 200, 300, 400, 500, 600, 700, 800, 900. |
font_style |
String | No | Default: "normal". Allowed: normal, italic, oblique. |
font_size |
String | No | Default: the source SVG's value (typically 12px). Number optionally followed by px, pt, em, rem, or %. |
font_variant |
String | No | Allowed: normal, small-caps. |
font_stretch |
String | No | Allowed: normal, condensed, expanded, ultra-condensed, extra-condensed, semi-condensed, semi-expanded, extra-expanded, ultra-expanded. |
letter_spacing |
String | No | "normal" or a number with optional unit (px, pt, em, rem). Negative values allowed for tighter spacing. |
word_spacing |
String | No | Same syntax as letter_spacing. |
text_decoration |
String | No | Allowed: none, underline, line-through, overline. |
text_anchor |
String | No | Horizontal anchor for the text relative to its (x, y). Allowed: start, middle, end. The reference SVGs use middle; overriding may misalign the inscription. |
fill |
String | No | Glyph fill color. Default: "#000000". Allowed: 3/4/6/8-digit hex (#RGB, #RGBA, #RRGGBB, #RRGGBBAA), none, transparent, currentColor. |
fill_opacity |
String | No | Decimal 0–1 (e.g., "0.5"). |
stroke |
String | No | Glyph stroke color. Default: "none". Same value syntax as fill. |
stroke_width |
String | No | Number optionally followed by px, pt, em, or rem. |
stroke_opacity |
String | No | Decimal 0–1. |
stroke_linecap |
String | No | Allowed: butt, round, square. |
stroke_linejoin |
String | No | Allowed: miter, round, bevel, miter-clip, arcs. |
stroke_dasharray |
String | No | Comma-separated numbers (max 10), e.g., "5,2,5". |
opacity |
String | No | Decimal 0–1. Applies to the entire glyph (fill + stroke composited together), distinct from fill_opacity / stroke_opacity. |
400.
Response
200 Success
Returns the professional stamp in the requested format: SVG (vector), PNG (raster, 4× scale), or PDF (print-ready, 8× scale).
HTTP/1.1 200 OK
Content-Type: image/svg+xml (or image/png, application/pdf)
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=3600
Content-Disposition: attachment; filename="stamp.svg" (SVG and PDF only)
[SVG/PNG/PDF data]
Content-Type: image/png-
Access-Control-Allow-Origin: *— CORS enabled for embedding in third-party sites -
Cache-Control: public, max-age=3600— Images cached for 1 hour
400 Bad Request
{
"error": "bad_request",
"message": "Missing required field: name"
}
-
Missing or empty required field (
name,license, orstate) - Unknown profession type
404 Not Found
{
"error": "not_found",
"message": "No stamp template available for this profession and state combination"
}
No stamp template exists for the requested profession in the given state. This may occur for less common professions or jurisdictions not yet supported.
500 Internal Server Error
{
"error": "render_failed",
"message": "Failed to generate stamp image"
}
An unexpected error occurred during stamp rendering. Please try again.
Stamp examples
cURL
# Get a PNG (default)
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{"name":"Jane Smith","license":"PE-12345","state":"california","profession":"engineer"}' \
--output stamp.png
# Get an SVG
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{"name":"Jane Smith","license":"PE-12345","state":"california","format":"svg"}' \
--output stamp.svg
# Get a PDF
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{"name":"Jane Smith","license":"PE-12345","state":"california","format":"pdf"}' \
--output stamp.pdf
JavaScript / Node.js
const response = await fetch('https://api.engineeringid.com/v1/stamp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Jane Smith',
license: 'PE-12345',
state: 'california',
profession: 'engineer',
format: 'svg' // or 'png', 'pdf'
})
});
const blob = await response.blob();
const url = URL.createObjectURL(blob);
document.getElementById('stamp').src = url;
Python
import requests
response = requests.post(
'https://api.engineeringid.com/v1/stamp',
json={
'name': 'Jane Smith',
'license': 'PE-12345',
'state': 'california',
'profession': 'engineer',
'format': 'svg',
'discipline': 'CIVIL',
}
)
with open('stamp.svg', 'wb') as f:
f.write(response.content)
Default profession
When profession is omitted, it defaults to "engineer":
# profession defaults to "engineer" when omitted
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{"name":"John Doe","license":"PE-98765","state":"texas"}' \
--output stamp.png
Style overrides
All 19 style parameters are optional. Omitted properties fall back to the defaults (sans-serif font stack, normal weight, black fill, no stroke).
# Customize font + colors. All 19 style params are optional.
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"license": "PE-12345",
"state": "california",
"format": "png",
"font_family": "Liberation Sans, sans-serif",
"font_weight": "bold",
"font_size": "14px",
"fill": "#003366",
"stroke": "none",
"letter_spacing": "0.5px"
}' \
--output stamp.png
Validation error (400)
Invalid style values return a 400 with a message that names the field, echoes the value you sent, and lists what the field accepts:
# Sending an invalid value returns a 400 with a message that names
# the field, echoes what you sent, and lists what it accepts.
curl -X POST https://api.engineeringid.com/v1/stamp \
-H "Content-Type: application/json" \
-d '{"name":"Jane","license":"PE-1","state":"CA","font_weight":"extra-bold"}'
# 400 Bad Request
# {
# "error": "bad_request",
# "message": "Invalid value for 'font_weight': \"extra-bold\". Allowed values: normal, bold, bolder, lighter, or 100/200/300/400/500/600/700/800/900."
# }
GET /v1/verify/:code
Look up a sealed document by its verification code. Returns document metadata, credential details, and seal information. No authentication required.
/v1/verify/:code
- Auth
- None
- Returns
application/json- Rate limit
- 60 / min per IP
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
code |
path | String | Yes | 12-character lowercase alphanumeric verification code printed on the sealed document (e.g., "abc123def456"). |
200 Response — valid code
{
"valid": true,
"document": {
"name": "Bridge Design Report.pdf",
"sealed_at": "2026-05-01T14:23:07Z",
"document_hash": "a1b2c3d4e5f6..."
},
"credential": {
"holder_name": "Jane Smith",
"license_type": "PE",
"license_number": "PE-12345",
"region_code": "CA",
"status": "active"
},
"seal": {
"verification_code": "abc123def456",
"sealed_at": "2026-05-01T14:23:07Z"
},
"blockchain": null,
"verification_url": "https://verify.engineeringid.com/abc123def456"
}
404 Response — code not found
{
"valid": false,
"error": "not_found",
"message": "No sealed document found with this verification code"
}
cURL example
curl https://api.engineeringid.com/v1/verify/abc123def456
POST /v1/verify/:code/match
Compare a client-provided SHA-256 hash against the sealed original to detect tampering. Use this when the verifier has a copy of the document and wants to confirm it hasn't been modified since sealing. No authentication required.
/v1/verify/:code/match
- Auth
- None
- Content-Type
application/json- Rate limit
- 60 / min per IP
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
code |
path | String | Yes | 12-character verification code from the sealed document. |
document_hash |
body | String | Yes | SHA-256 hash of your copy of the document. Accepts raw 64-char hex or a `sha256:` / `sha-256:` prefixed string. |
200
— check the match
boolean.
Request body
{
"document_hash": "sha256:a1b2c3d4e5f6..."
}
200 Response — match
{
"match": true,
"message": "Document matches the sealed original",
"sealed_at": "2026-05-01T14:23:07Z",
"verification_url": "https://verify.engineeringid.com/abc123def456"
}
200 Response — tampered / mismatch
{
"match": false,
"message": "Document does not match. The file may have been modified after sealing.",
"sealed_at": "2026-05-01T14:23:07Z",
"verification_url": "https://verify.engineeringid.com/abc123def456"
}
cURL example
curl -X POST https://api.engineeringid.com/v1/verify/abc123def456/match \
-H "Content-Type: application/json" \
-d '{"document_hash":"sha256:a1b2c3d4e5f6..."}'
GET /v1/verify/:code/stats
Returns aggregate verification statistics for a sealed document: how many times it has been verified and when it was sealed. Useful for embedding a "verified N times" badge. No sensitive document data is included. No authentication required.
/v1/verify/:code/stats
- Auth
- None
- Returns
application/json- Rate limit
- 60 / min per IP
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
code |
path | String | Yes | 12-character lowercase alphanumeric verification code printed on the sealed document (e.g., "abc123def456"). |
200 Response
{
"verification_code": "abc123def456",
"verification_count": 14,
"sealed_at": "2026-05-01T14:23:07Z"
}
cURL example
curl https://api.engineeringid.com/v1/verify/abc123def456/stats
POST /v1/verify/pdf
Verify a sealed PDF by combining its SHA-256 hash and embedded verification code in a single request. Used by the Chrome extension and Acrobat plugin, which compute the hash client-side and extract the code from the PDF metadata. No authentication required.
/v1/verify/pdf
- Auth
- None
- Content-Type
application/json- Rate limit
- 60 / min per IP
Parameters
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
hash |
body | String | Yes | SHA-256 hash of the PDF file. Raw 64-char hex or `sha256:`-prefixed form accepted. |
code |
body | String | Yes | 12-character lowercase alphanumeric verification code embedded in the PDF. |
200. The status
field is "valid", "tampered", or "not_found". Validation errors return 422.
Request body
{
"hash": "a1b2c3d4e5f6...",
"code": "abc123def456"
}
200 Response — valid
{
"status": "valid",
"seal": {
"verification_code": "abc123def456",
"sealed_at": "2026-05-01T14:23:07Z",
"professional_name": "Jane Smith",
"license_number": "PE-12345"
}
}
200 Response — tampered
{
"status": "tampered"
}
cURL example
curl -X POST https://api.engineeringid.com/v1/verify/pdf \
-H "Content-Type: application/json" \
-d '{"hash":"a1b2c3d4e5f6...","code":"abc123def456"}'
Rate limiting
All limits are per IP address. Exceeding the limit returns
429 Too Many Requests
— implement exponential backoff and cache responses when possible.
| Endpoint group | Limit | Window | Reason |
|---|---|---|---|
POST /v1/stamp |
10 / min | 60 s | SVG-to-PNG/PDF rasterization invokes a Rust NIF — CPU-intensive |
GET /v1/verify/:code
POST /v1/verify/:code/match
GET /v1/verify/:code/stats
POST /v1/verify/pdf
|
60 / min | 60 s | Prevents brute-force enumeration of verification codes |
Error handling
Best practices
-
Always check the HTTP status code.
Success is
200; errors are400,404,429, or500. - For non-200 responses, parse the JSON error object to get the error code and message.
-
Implement exponential backoff
if you receive rate limit errors (
429). -
Cache responses when possible.
The API returns
Cache-Control: public, max-age=3600, so browser and CDN caching is encouraged.
Common issues
| Issue | Cause | Solution |
|---|---|---|
| 400 Missing required field | name, license, or state is missing or empty | Verify all required fields are present and non-empty strings |
| 400 Unknown profession | profession is not a recognized type | Use one of: engineer, architect, land-surveyor, landscape-architect (or engineer subtypes) |
| 400 Unknown format | format is not svg, png, or pdf | Use format=svg, format=png (default), or format=pdf |
| 400 Invalid style override | A style parameter (font_*, fill, stroke, etc.) was sent with a value that doesn't match its allowed pattern. | Consult the parameter table for that field. Hex colors must be #RGB / #RGBA / #RRGGBB / #RRGGBBAA; sizes accept px / pt / em / rem / %; opacity values are decimals 0–1. |
| 404 No stamp template | Profession + state combination not supported | Verify the state name. Some professions may not have templates in all states. |
| 429 Rate limit exceeded | More than 10 requests in 60 seconds from your IP | Implement exponential backoff; cache results when possible |
| 500 Render failed | Unexpected error in PNG/PDF rasterization (SVG format avoids this) | Try format=svg instead, or retry the request |
Contact us
Get in touch
Tell us what you're building. We answer integration, schema, auth, rate-limit, SDK, and pricing questions — usually within a business day.
No spam, unsubscribe anytime. We respect your privacy.
Need help integrating?
Tell us about your use case and our developer team will respond with implementation details.