Who is the API for? The API is available to active contract customers. Trial accounts cannot use it or create an API key. This documentation is public so you can check in advance how PortraitFlow fits into your HR / IT workflow. We are happy to answer questions: studio@portraitflow.ch.
1. Overview
The API is a JSON interface (REST). With it, your system can:
- query your projects,
- create employees – one at a time or up to 200 at once,
- query, update and delete employees, including their status,
- download finished portraits that the employee has approved (JPEG with background or cut-out PNG, optionally in your configured image sizes).
The process behind the scenes stays the same as in the portal: PortraitFlow invites the employees, a photographer edits the images, the employees approve their image. The API handles transferring the data and collecting the results.
2. Typical workflow
- Your IT creates an API key in the portal (active contract customers only).
- Your system queries the projects and remembers the project id.
- Your system transfers the employees. Send your personnel number as "mitarbeiternummer" – it later serves as the key to match each image to the right record.
- PortraitFlow invites the employees and edits the photos. Invitations are deliberately triggered by PortraitFlow, not through the API.
- A regular job (e.g. hourly or nightly) queries the employees with a finished image and downloads the images.
- Your system stores the image on the matching employee (matched by personnel number).
Note: every employee needs an email address because the invitation is sent by email. Email address and personnel number are unique per project.
3. Access and authentication
- The API is available to active contract customers. Trial accounts cannot use it.
- You create and revoke API keys in the portal under Account → API access. Up to 5 active keys per account are possible (e.g. for key rotation).
- A key is shown only once at creation. Store it like a password. If it is lost or compromised, revoke it and create a new one.
Send the key with every request in the "Authorization" header. Base URL: https://portraitflow.ch/api/v1
curl https://portraitflow.ch/api/v1/projects \
-H "Authorization: Bearer pf_IhrSchluessel"
4. Endpoints at a glance
| Endpoint | Purpose |
|---|---|
GET /projects | List your projects |
GET /projects/{projectId}/employees | List the employees of a project (filters, pages) |
POST /projects/{projectId}/employees | Create one or several employees |
GET /employees/{employeeId} | Get one employee |
PATCH /employees/{employeeId} | Update an employee |
DELETE /employees/{employeeId} | Delete an employee including photos |
GET /employees/{employeeId}/photo | Download the finished image |
GET /image-sizes | List your configured image sizes |
5. Endpoints in detail
Query projects
GET https://portraitflow.ch/api/v1/projects
{
"data": [
{ "id": 11, "projektnummer": "P-2026-014", "name": "Porträts 2026", "status": "active",
"start_date": "2026-10-01", "deadline": "2026-11-30", "employees_count": 700 }
]
}
Create employees
Either send one employee directly in the body or several as an "employees" list (maximum 200 per call). The call is all-or-nothing: if one entry is invalid, none is created, and the error names the affected entry (e.g. "employees.1.email"). "mitarbeiternummer" and "locale" (de/en) are optional; without a number PortraitFlow assigns one automatically. The response contains the created employees in the same order.
curl -X POST https://portraitflow.ch/api/v1/projects/11/employees \
-H "Authorization: Bearer pf_IhrSchluessel" \
-H "Content-Type: application/json" \
-d '{"employees":[
{"first_name":"Anna","last_name":"Muster","email":"anna.muster@example.com","mitarbeiternummer":"PERS-1042"},
{"first_name":"Ben","last_name":"Beispiel","email":"ben.beispiel@example.com"}]}'
Tip: when synchronising again, transfer only new employees. Email addresses that already exist are rejected (and with them the whole call). Query the existing employees first.
Query employees
Optional filters (as query parameters):
status | pending, uploaded, edited, retake |
mitarbeiternummer | Exact personnel number |
photo_ready=1 | Only employees with a finished, approved image |
per_page, page | Page size (1–200, default 50) and page number |
GET https://portraitflow.ch/api/v1/projects/11/employees?photo_ready=1&per_page=200&page=1
{
"data": [
{ "id": 23, "project_id": 11, "mitarbeiternummer": "PERS-1042",
"first_name": "Anna", "last_name": "Muster", "email": "anna.muster@example.com",
"locale": null, "status": "edited",
"invited_at": "2026-10-20T08:00:00+02:00", "uploaded_at": "2026-10-21T10:15:00+02:00",
"photo": {
"approved": true,
"edited_at": "2026-10-22T14:00:00+02:00",
"standard_url": "https://portraitflow.ch/api/v1/employees/23/photo",
"transparent_url": null
} }
],
"links": { "next": null },
"meta": { "current_page": 1, "last_page": 1, "per_page": 200, "total": 1 }
}
| Field | Meaning |
|---|---|
status | pending = no photo yet · uploaded = photo received · edited = edited photo available · retake = new photo requested |
photo.approved | The employee approved the edited image |
photo.edited_at | Time of the last edit. Changes after a retake – this is how you recognise an updated image. |
photo.standard_url | Download address of the JPEG with background – only set when finished and approved |
photo.transparent_url | Download address of the cut-out PNG – only set when available and approved |
A single employee is returned by GET /employees/{employeeId} in the same format.
Update and delete employees
With "PATCH" send only the fields that change (first_name, last_name, email, mitarbeiternummer, locale). If the email address of an already invited employee who has not uploaded a photo yet changes, a new invitation is sent automatically to the corrected address (response: meta.invitation_resent). "DELETE" permanently removes the employee including their photos and responds with 204.
curl -X PATCH https://portraitflow.ch/api/v1/employees/23 \
-H "Authorization: Bearer pf_IhrSchluessel" \
-H "Content-Type: application/json" \
-d '{"email":"anna.muster@neuefirma.example"}'
6. Fetching finished images
An image can only be fetched once the edited photo exists and the employee has approved it. With "photo_ready=1" (see above) you query exactly these employees. For the download you also send your API key:
| Parameter | Effect |
|---|---|
variant=standard | JPEG with background (default). Always a real JPEG; transparent areas become white. |
variant=transparent | Cut-out PNG with transparent background. Always a real PNG. |
size=0, size=1 … | One of your configured image sizes (index from "GET /image-sizes"). Without "size" you get the image in full size. Both variants are cropped identically (same section, aligned on the eyes as in the portal); the PNG keeps its transparency. |
curl -o anna.jpg "https://portraitflow.ch/api/v1/employees/23/photo?size=0" \
-H "Authorization: Bearer pf_IhrSchluessel"
GET https://portraitflow.ch/api/v1/image-sizes
{ "data": [ { "index": 0, "label": "Quadrat", "width": 500, "height": 500 } ] }
PortraitFlow sets the image sizes (up to 4) in your account. If you need other sizes, write to us.
7. Errors and limits
| Code | Meaning |
|---|---|
| 401 | API key missing, invalid or revoked |
| 403 | API access is not enabled for this account |
| 404 | Unknown id, or it belongs to another customer. For images: no edited photo yet ("not_available") or unknown size ("unknown_size"). |
| 409 | The employee has not approved the image yet ("not_approved") – try again later. When creating: brief conflict while assigning numbers, send again. |
| 422 | Invalid input. The response gives the reason per field ("errors"). |
| 429 | Too many requests. Wait for the time in the "Retry-After" header (seconds). |
- Limit: 120 requests per minute per key. Downloading 700 images therefore takes about 6 minutes.
- At most 200 employees per create call.
- All responses are JSON (except the image download). Data is strictly limited to your account.
8. Practical examples
Example 1: 700 employees from the HR system
- Your job reads the 700 employees from the HR system and sends them in 4 calls (200, 200, 200, 100) to "POST /projects/{projectId}/employees", each with the personnel number as "mitarbeiternummer".
- PortraitFlow invites and edits, the employees approve their image.
- A regular job fetches all employees with "photo_ready=1", downloads the new images and stores them in the HR system under the matching personnel number.
Example script (Python) that fetches images and remembers the ones already taken over:
import requests
H = {"Authorization": "Bearer pf_IhrSchluessel"}
BASE = "https://portraitflow.ch/api/v1"
PROJECT, SIZE = 11, 0 # Projekt-ID, Index Ihrer Bildgrösse
def ready_employees():
page = 1
while True:
r = requests.get(f"{BASE}/projects/{PROJECT}/employees", headers=H,
params={"photo_ready": 1, "per_page": 200, "page": page}).json()
yield from r["data"]
if page >= r["meta"]["last_page"]:
break
page += 1
for e in ready_employees():
key = (e["mitarbeiternummer"], e["photo"]["edited_at"]) # edited_at ändert sich bei Neuaufnahme
if key in already_imported: # Ihre eigene Merkliste
continue
img = requests.get(e["photo"]["standard_url"], headers=H, params={"size": SIZE})
img.raise_for_status()
hr_system.set_photo(personnel_no=e["mitarbeiternummer"], jpeg=img.content)
already_imported.add(key)
Example 2: Ongoing operation with access badge and intranet
As soon as your HR system records a new person, it sends them individually to "POST /projects/{projectId}/employees". Every night a job fetches all newly finished images: the small image size ("size=1") for the intranet, the cut-out PNG ("variant=transparent") for the badge, placed onto the badge template. When someone leaves, the HR system calls "DELETE /employees/{employeeId}" – this also removes the photos at PortraitFlow.
9. OpenAPI file
Your developers can use the machine-readable description of the interface (OpenAPI 3). It lets you set up clients and test tools (e.g. Postman) automatically: portraitflow-api-v1.yaml
More guides: How PortraitFlow works, Inviting and importing employees, Image formats and download.
Frequently asked questions
Who can use the API?
Active contract customers. Trial accounts cannot use the API or create API keys, but the documentation is open to everyone.
How does my system receive the finished images?
A regular job queries employees with a finished, approved image (photo_ready=1) and downloads the images via the address provided.
How many employees can I transfer per call?
Up to 200. For 700 employees, four calls are enough.
Is there a request limit?
Yes: 120 requests per minute per API key. If exceeded, the API responds with 429 and the waiting time in the Retry-After header.
Does the API also trigger the invitations?
No. Employees are created through the API; PortraitFlow deliberately triggers the invitation itself.
Try PortraitFlow without obligation
Free trial access, no credit card, set up in minutes.