PortraitFlow Logo PortraitFlowby Video & Photoworkers

PortraitFlow Help

API documentation

With the PortraitFlow API you transfer employee data from your HR system and take over finished portraits automatically. Workflow, endpoints, examples and OpenAPI file.

Last updated:

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

  1. Your IT creates an API key in the portal (active contract customers only).
  2. Your system queries the projects and remembers the project id.
  3. 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.
  4. PortraitFlow invites the employees and edits the photos. Invitations are deliberately triggered by PortraitFlow, not through the API.
  5. A regular job (e.g. hourly or nightly) queries the employees with a finished image and downloads the images.
  6. 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

EndpointPurpose
GET /projectsList your projects
GET /projects/{projectId}/employeesList the employees of a project (filters, pages)
POST /projects/{projectId}/employeesCreate 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}/photoDownload the finished image
GET /image-sizesList 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):

statuspending, uploaded, edited, retake
mitarbeiternummerExact personnel number
photo_ready=1Only employees with a finished, approved image
per_page, pagePage 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 }
}
FieldMeaning
statuspending = no photo yet · uploaded = photo received · edited = edited photo available · retake = new photo requested
photo.approvedThe employee approved the edited image
photo.edited_atTime of the last edit. Changes after a retake – this is how you recognise an updated image.
photo.standard_urlDownload address of the JPEG with background – only set when finished and approved
photo.transparent_urlDownload 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:

ParameterEffect
variant=standardJPEG with background (default). Always a real JPEG; transparent areas become white.
variant=transparentCut-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

CodeMeaning
401API key missing, invalid or revoked
403API access is not enabled for this account
404Unknown id, or it belongs to another customer. For images: no edited photo yet ("not_available") or unknown size ("unknown_size").
409The employee has not approved the image yet ("not_approved") – try again later. When creating: brief conflict while assigning numbers, send again.
422Invalid input. The response gives the reason per field ("errors").
429Too 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

  1. 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".
  2. PortraitFlow invites and edits, the employees approve their image.
  3. 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.