REST API v1

API Documentation

Public integration guide and machine-readable resources for ProBG Time Tracking.

Base API URL
https://tracker.probg.com/api/v1

REST API v1

ProBG Time Tracking API v1 provides read-only access to clients, projects, and tracked time that belong to the token's Account. It is intended for CRM/ERP integrations, BI reporting, external reporting, and data synchronization.

The API never accepts an external account_id to select tenancy. The Account is determined exclusively by the Bearer token.

Quick start

  1. Open API in the administration as an Account Administrator.
  2. Create a separate token for the integration.
  3. Grant only the required scopes: clients:read, projects:read, and/or time:read.
  4. Copy the token immediately — its full value is displayed only once.
  5. Send the token with every request using HTTP Bearer authentication.
curl -H "Authorization: Bearer <TOKEN>" \
  -H "Accept: application/json" \
  "https://tracker.probg.com/api/v1/clients"

Plan availability

The API works only while the Account has the api entitlement enabled.

The default plan presets enable API access for:

  • Agency
  • Enterprise

A Platform Administrator may change the entitlement at plan level or by Account override.

An existing token stops working immediately when:

  • it is revoked;
  • it expires;
  • the Account is suspended;
  • the current subscription no longer includes API access.

Authentication

Base URL:

https://tracker.probg.com/api/v1

Required headers:

Authorization: Bearer ptt_...
Accept: application/json

Tokens are Account-scoped. Only a SHA-256 hash is stored; the plaintext token cannot be recovered.

Token expiration

When creating a token, choose:

  • 30 days;
  • 90 days;
  • 365 days;
  • no automatic expiration.

A token without automatic expiration must be revoked manually when the integration is no longer used.

Scopes

Scope Grants
clients:read Client list and client details
projects:read Project list and project details
time:read Time-entry list and details

A token without the required scope receives HTTP 403 with error.code = insufficient_scope.

Use least privilege: do not grant all three scopes when an integration needs only one resource.

Rate limits

Per token:

  • 120 requests / minute;
  • 3000 requests / hour.

Additional IP safety ceiling:

  • 600 requests / minute;
  • 10,000 requests / hour.

When a limit is reached, Laravel returns HTTP 429 Too Many Requests.

Pagination

All list endpoints are paginated.

Parameter Type Default Limit
page integer 1 Page number
per_page integer 25 1–100

Example:

{
  "data": [],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "last_page": 1,
    "total": 0
  },
  "links": {
    "first": "...",
    "last": "...",
    "prev": null,
    "next": null
  }
}

Follow links.next until it becomes null.

Data conventions

  • Date-only values use YYYY-MM-DD.
  • Date/time values use ISO 8601.
  • Durations are seconds.
  • Decimal money values are returned as decimal strings or null to preserve precision.
  • All responses are JSON.
  • IDs are integers.

Clients

GET /clients

Requires clients:read.

Returns clients in the token's Account.

Filters

Parameter Type Description
q string, max 100 Searches name, company, contact, email, and company ID
status enum active (default), inactive, archived, all
updated_since date/datetime Records updated at or after the supplied value
per_page integer 1–100

Example:

curl -H "Authorization: Bearer <TOKEN>" \
  -H "Accept: application/json" \
  "https://tracker.probg.com/api/v1/clients?status=all&updated_since=2026-10-01T00:00:00%2B03:00&per_page=50"

Client fields

Field Type
id integer
name string
company string/null
contact_name string/null
email string/null
phone string/null
address string/null
company_id string/null
is_active boolean
projects_count integer
tracked_seconds integer
archived_at ISO 8601/null
created_at ISO 8601/null
updated_at ISO 8601/null

GET /clients/{id}

Requires clients:read.

Returns one client. Archived clients remain addressable by ID.

An ID from another Account returns 404 without disclosing whether that resource exists.

Example response:

{
  "data": {
    "id": 42,
    "name": "Example Client",
    "company": "Example Ltd",
    "contact_name": "Jane Doe",
    "email": "jane@example.com",
    "phone": "+359...",
    "address": "Sofia",
    "company_id": "BG123456789",
    "is_active": true,
    "projects_count": 3,
    "tracked_seconds": 184500,
    "archived_at": null,
    "created_at": "2026-09-10T09:20:00+03:00",
    "updated_at": "2026-10-05T17:14:00+03:00"
  }
}

Projects

GET /projects

Requires projects:read.

Filters

Parameter Type Description
q string, max 100 Searches project name and description
client_id integer Projects for one client
status enum active (default), completed, archived, all
updated_since date/datetime Records updated at or after the supplied value
per_page integer 1–100

Example:

curl -H "Authorization: Bearer <TOKEN>" \
  -H "Accept: application/json" \
  "https://tracker.probg.com/api/v1/projects?client_id=42&status=all"

Project fields

Field Type Notes
id integer
client_id integer
client object/null id, name, company
name string
description string/null
status string
billing_model string hourly, fixed_fee, retainer, non_billable
hourly_rate decimal string/null Stored project rate
effective_hourly_rate decimal string/null Effective rate for Hourly projects
billing_currency string Project billing currency
fixed_fee_amount decimal string/null
retainer_amount decimal string/null
estimated_seconds integer/null
tracked_seconds integer
starts_at YYYY-MM-DD/null
ends_at YYYY-MM-DD/null
created_at ISO 8601/null
updated_at ISO 8601/null

Internal labor-cost snapshots are not exposed.

GET /projects/{id}

Requires projects:read.

Returns one project from the current Account.

Time entries

GET /time-entries

Requires time:read.

Filters

Parameter Type Description
client_id integer Entries for one client
project_id integer Entries for one project
task_id integer Entries for one task
user_id integer Entries for one user
from YYYY-MM-DD Inclusive start date
to YYYY-MM-DD Inclusive end date; must be >= from
billable boolean true/false or 1/0; empty means no filter
billed boolean true/false or 1/0; empty means no filter
q string, max 100 Searches title and description
updated_since date/datetime Records updated at or after the supplied value
per_page integer 1–100

Project-month export example:

curl -H "Authorization: Bearer <TOKEN>" \
  -H "Accept: application/json" \
  "https://tracker.probg.com/api/v1/time-entries?project_id=12&from=2026-10-01&to=2026-10-31&per_page=100"

Time-entry fields

Field Type
id integer
user_id integer/null
client_id integer/null
project_id integer/null
task_id integer/null
user object/null — id, name
client object/null — id, name
project object/null — id, name
task object/null — id, title
work_date YYYY-MM-DD/null
started_at ISO 8601/null
ended_at ISO 8601/null
duration_seconds integer
source string
title string
description string/null
is_billable boolean
is_billed boolean
hourly_rate decimal string/null
amount decimal string/null
created_at ISO 8601/null
updated_at ISO 8601/null

The API intentionally excludes:

  • cost_rate;
  • cost_amount;
  • private notes.

GET /time-entries/{id}

Requires time:read.

Returns one time entry from the current Account.

Error responses

401 — missing Bearer token

{
  "error": {
    "code": "unauthenticated",
    "message": "A bearer token is required."
  }
}

401 — invalid, expired, or revoked token

{
  "error": {
    "code": "invalid_token",
    "message": "The API token is invalid, expired, or revoked."
  }
}

403 — Account unavailable

{
  "error": {
    "code": "account_unavailable",
    "message": "The account is not available."
  }
}

403 — subscription no longer includes API access

{
  "error": {
    "code": "feature_unavailable",
    "message": "API access is not included in the current subscription."
  }
}

403 — missing scope

{
  "error": {
    "code": "insufficient_scope",
    "message": "The API token does not have the required scope [projects:read]."
  }
}

404 — resource unavailable

Unknown IDs, zero, IDs outside the supported positive integer range, and IDs that belong to another Account return 404.

422 — validation error

Invalid query parameters use Laravel's standard JSON validation response:

{
  "message": "The given data was invalid.",
  "errors": {
    "per_page": [
      "The per page field must not be greater than 100."
    ]
  }
}

429 — rate limit

Reduce request frequency and apply retry/backoff logic.

Integration recipes

Incremental synchronization

Store a checkpoint after a fully successful import and use updated_since on the next run.

GET /clients?updated_since=2026-10-06T08:00:00Z
GET /projects?updated_since=2026-10-06T08:00:00Z
GET /time-entries?updated_since=2026-10-06T08:00:00Z

Do not use the client machine's current clock as proof that all changes have been consumed. Advance the checkpoint only after all pages complete successfully.

Projects for one client

GET /projects?client_id=42&status=all

Tracked time for a period

GET /time-entries?from=2026-10-01&to=2026-10-31&per_page=100

Security recommendations

  • Create a separate token for every integration.
  • Grant the minimum required scopes.
  • Never commit the token to Git or expose it in frontend JavaScript, screenshots, or public logs.
  • Store it in a secret manager or environment variable.
  • Always use HTTPS.
  • Revoke compromised or unused tokens immediately.
  • Rotate long-lived tokens periodically.
  • Do not retry 401 responses indefinitely; inspect token lifecycle.
  • Use exponential backoff after 429.

Versioning

Breaking authentication or response-contract changes require a new namespace such as /api/v2.

Within v1, the API may add:

  • optional response fields;
  • optional query filters;
  • new endpoints.

Integrations must not assume response objects contain only the currently documented fields.

Current limitations

API v1 is currently read-only.

Not yet part of v1:

  • create/update/delete operations;
  • Tasks endpoint;
  • Reports endpoint;
  • Invoices endpoint;
  • Webhooks;
  • OAuth/client credentials;
  • bulk export endpoint.

These capabilities require separate versioned contracts and security review.

Public documentation and Postman

The documentation is available without signing in:

  • /api/docs — English;
  • /api/docs/en — English;
  • /api/docs/bg — Bulgarian;
  • /api/docs/openapi.yaml — OpenAPI 3.1 specification;
  • /api/docs/postman.json — Postman collection.

Using the Postman collection

  1. Download /api/docs/postman.json.
  2. Choose Import in Postman and import the JSON file.
  3. Open the collection variables.
  4. Set base_url — the default is https://tracker.probg.com/api/v1.
  5. Paste the API token into token.
  6. Set client_id, project_id, and time_entry_id when needed.
  7. Send the prepared requests from the Clients, Projects, and Time Entries folders.

Bearer authentication is configured at collection level through {{token}}. Optional query parameters are included in disabled state and can be enabled as needed.

OpenAPI

Use the Download OpenAPI action in the administration documentation page to obtain an OpenAPI 3.1 YAML specification suitable for Postman, Insomnia, Swagger UI/Editor, Redoc, and SDK-generation tools.

Checkpoint dates and timezone

updated_since is inclusive. ISO 8601 offsets (Z, +03:00, etc.) are converted to the application/database storage timezone before comparison. Date-only values mean midnight in that timezone; datetimes without an offset also use it. Stored updated_at values have second precision; a fractional checkpoint after a stored second excludes that second. Equivalent instants always select the same records.

Use status=all when synchronizing clients or projects. These endpoints filter the resource’s own updated_at; changes to related aggregates (such as tracked_seconds) do not necessarily update that timestamp. There is no deletion feed or stable snapshot across pages. Integrations should overlap checkpoints, deduplicate by ID, and periodically reconcile full data.

Billing status checkpoints

Issuing or cancelling an invoice advances updated_at on its linked time entries in the same transaction that changes is_billed. The next inclusive updated_since query therefore includes those changes. This only changes synchronization metadata; locked durations and financial snapshots are preserved. Duplicate actions do not advance the checkpoint; failed issuance rolls it back. Cancellation follows the linked historical entries even after a client/account transfer, while API visibility remains scoped to the current token account.

Existing integrations must perform one full time-entry reconciliation after deploying this correction: billing changes made before deployment are not backfilled. Overlap checkpoints and periodically reconcile data as described above; deletion feeds and stable pagination snapshots remain outside v1.