Getting Started
Authentication, base URL, rate limiting, and how to use this documentation.
Overview
Public REST API for integrating HR, CRM, or ERP systems with exportlab.io group shoot galleries, plus public endpoints for custom check-in flows.
List participants and retrieve their assigned photos, all scoped to your tenant.
Base URL
https://api.exportlab.io/v1Authentication
Every request requires two headers.
Get your API key in the exportlab.io admin under Settings → Integration → API Keys.
Go to Settings → Integration, scroll to the API Keys section, click New key, give it a name (example: HR Integration), and copy the key immediately. It is only shown once.
The API is designed for server-to-server use. Never expose your API key in a browser or client-side code.
Public check-in frontend endpoints (GET /group-shoot/checkin/{token} and POST /group-shoot) do not require an API key, but they still require a valid tenant slug.
Server-side Group Shoot API endpoints, such as listing persons, importing persons, photos, form fields, shooting days, and stats, require both tenant slug and API key and are available on Creator and higher plans.
curl -X GET "https://api.exportlab.io/v1/group-shoot/checkin/{TOKEN}" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Tenant-Slug: YOUR_TENANT"| Header | Description |
|---|---|
| Authorization: Bearer | Your API key. Also accepted as X-Api-Key: |
| X-Tenant-Slug: | Your tenant identifier (example: acme) |
Response format
All responses are JSON.
The X-Request-Id response header contains the same request ID. Include it in support requests.
{
"ok": true,
"requestId": "6322678e-7898-43eb-baeb-5de42b07ca7f"
}Rate limiting
The API enforces a limit of 60 requests per minute per API key. If you exceed the limit you receive a 429 response.
Wait until X-RateLimit-Reset before retrying. For bulk imports, add a small delay between requests to stay within the limit.
{
"ok": false,
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Max 60 requests per minute. Retry after 42s.",
"requestId": "..."
}| Header | Description |
|---|---|
| X-RateLimit-Limit | Max requests allowed per window (60) |
| X-RateLimit-Remaining | Requests remaining in the current window |
| X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
Error codes
| HTTP | error | Description |
|---|---|---|
| 400 | missing_tenant_slug | X-Tenant-Slug header is missing |
| 400 | invalid_tenant | Tenant slug could not be resolved |
| 400 | not_a_groupshoot_gallery | The gallery token exists but is not a group shoot |
| 400 | token_required | Gallery token is missing |
| 400 | name_required | Name is missing or empty |
| 400 | email_required | Email is missing or empty in the check-in submission |
| 400 | terms_not_accepted | A paid package was selected but package terms were not accepted |
| 400 | connect_not_ready_for_payments | Stripe Connect is not ready for package checkout |
| 400 | payment_access_not_approved | Seller Verification or package payment access is not approved |
| 400 | package_amount_exceeds_limit | Selected paid package exceeds the allowed transaction limit |
| 400 | invalid_json | Request body is not valid JSON |
| 401 | api_key_required | No API key provided |
| 401 | invalid_api_key | API key is wrong, revoked, or disabled |
| 404 | gallery_not_found | Gallery token does not exist |
| 404 | person_not_found | Person ID does not exist in this gallery |
| 400 | persons_required | POST /persons called with an empty or missing persons array |
| 400 | too_many_persons | More than 500 persons in a single import request |
| 403 | plan_upgrade_required | Tenant is not on Creator plan or above |
| 405 | method_not_allowed | Wrong HTTP method for this endpoint (POST /persons is the only non-GET endpoint) |
| 409 | shooting_day_full | The selected shooting day has reached its configured capacity |
| 429 | rate_limit_exceeded | Too many requests. See X-RateLimit-Reset header |
| 500 | cdn_signing_unavailable | CloudFront signing failed — retry the request |
| 500 | internal_error | Server error, contact support with the requestId |
| 503 | plan_check_unavailable | Could not verify plan eligibility — retry the request |
URL aliases
Both URL patterns are equivalent and fully supported.
| Method | Pattern | Description |
|---|---|---|
| GET | /group-shoot/checkin/{token}/form-fields | Primary |
| GET | /groupshoot/{token}/form-fields | Short alias |
| GET | /group-shoot/checkin/{token}/days | Primary |
| GET | /groupshoot/{token}/days | Short alias |
| GET | /group-shoot/checkin/{token}/stats | Primary |
| GET | /groupshoot/{token}/stats | Short alias |
| GET | /group-shoot/checkin/{token}/persons | Primary |
| GET | /groupshoot/{token}/persons | Short alias |
| POST | /group-shoot/checkin/{token}/persons | Primary (import) |
| POST | /groupshoot/{token}/persons | Short alias (import) |
| GET | /group-shoot/checkin/{token}/persons/{personId}/photos | Primary |
| GET | /groupshoot/{token}/persons/{personId}/photos | Short alias |