Tickets, SSH keys and account
Tickets
GET /tickets
Permissions: tickets:read
| Parameter | Type | Required | Values |
|---|---|---|---|
limit | integer | no, default 20 | 1 to 100 |
cursor | string | no | offset cursor |
status | string | no | OPEN, IN_PROGRESS, WAITING_CUSTOMER, WAITING_SUPPORT, RESOLVED, CLOSED |
Sorted on the last update, not on the creation date.
| Field | Type | Notes |
|---|---|---|
ticketNumber | string | the identifier every ticket route takes |
subject | string | |
status | string | the values above |
priority | string | LOW, MEDIUM, HIGH, URGENT |
category | string | GENERAL, TECHNICAL, BILLING, ACCOUNT |
serviceCode | string or null | the service concerned |
createdAt, updatedAt | integer | epoch milliseconds |
lastMessageAt | integer or null | epoch milliseconds |
messageCount | integer | |
unread | boolean | support wrote something you have not read |
GET /tickets/{ticketNumber}
Permissions: tickets:read
This call marks the ticket as read.
Carries the summary fields, without unread and messageCount, and adds:
| Field | Type | Notes |
|---|---|---|
description | string | the first message, repeated |
closedAt | integer or null | epoch milliseconds |
messages[] | array | the conversation, in order |
messages[] field | Type | Notes |
|---|---|---|
id | integer | |
sender | string | CUSTOMER, SUPPORT, SYSTEM |
content | string | |
attachments | integer | the count of attachments. They download from the dashboard |
createdAt | integer | epoch milliseconds |
POST /tickets
Permissions: tickets:write
| Field | Type | Required | Values | Where it comes from |
|---|---|---|---|---|
subject | string | yes | 200 characters at most | - |
message | string | yes | 10 000 characters at most | - |
category | string or null | no, default GENERAL | GENERAL, TECHNICAL, BILLING, ACCOUNT | - |
priority | string or null | no, default MEDIUM | LOW, MEDIUM, HIGH, URGENT | - |
serviceCode | string or null | no | - | GET /services → code |
A value outside its enum answers 400 VALIDATION_ERROR:category or 400 VALIDATION_ERROR:priority, never the default.
curl -X POST -H "Authorization: Bearer $FRESHPERF_KEY" \ -H "Content-Type: application/json" \ -d '{"subject":"Disk full","message":"The disk has been full since this morning.","category":"TECHNICAL","priority":"HIGH","serviceCode":"SRV-TJUAQ1-1951"}' \ https://api.freshperf.fr/v1/tickets
Answers 201 with the whole ticket, messages[] included.
| Error | Status | When |
|---|---|---|
VALIDATION_ERROR:subject | 400 | empty, or beyond 200 characters |
VALIDATION_ERROR:message | 400 | empty, or beyond 10 000 characters |
VALIDATION_ERROR:category, VALIDATION_ERROR:priority | 400 | value outside its enum |
SUBJECT_TOO_SHORT | 400 | subject too short for support |
SERVICE_NOT_FOUND | 404 | serviceCode unknown, or outside the key's reach |
POST /tickets/{ticketNumber}/messages
Permissions: tickets:write
| Field | Type | Required | Values |
|---|---|---|---|
message | string | yes | 10 000 characters at most |
Answers 201 with the message created. Replying to a closed ticket reopens it.
POST /tickets/{ticketNumber}/close
Permissions: tickets:write
No body. Answers 200 with the ticket summary, status at CLOSED.
SSH keys
The account's SSH keys are offered when ordering a VPS. A removed key stays installed on already-provisioned servers until they are reinstalled.
GET /ssh-keys and GET /ssh-keys/{id}
Permissions: account.ssh_keys:read
The whole list, unpaginated.
| Field | Type | Notes |
|---|---|---|
id | integer | pass it back to delete |
label | string | |
keyType | string or null | e.g. ssh-ed25519 |
fingerprint | string or null | SHA-256 in hex |
comment | string or null | the end-of-line comment |
publicKey | string or null | the full line. Present on the single read and on creation, null in the list |
createdAt | integer | epoch milliseconds |
lastUsedAt | integer or null | epoch milliseconds |
POST /ssh-keys
Permissions: account.ssh_keys:write
| Field | Type | Required | Values |
|---|---|---|---|
label | string | yes | - |
publicKey | string | yes | an OpenSSH public key line |
curl -X POST -H "Authorization: Bearer $FRESHPERF_KEY" \ -H "Content-Type: application/json" \ -d '{"label":"laptop","publicKey":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... admin@laptop"}' \ https://api.freshperf.fr/v1/ssh-keys
Answers 201 with the key, publicKey included.
| Error | Status | When |
|---|---|---|
SSH_KEY_FORMAT_INVALID | 400 | not a public key line |
DUPLICATE_KEY:<id> | 400 | the same key is already on the account. The suffix gives its id |
DELETE /ssh-keys/{id}
Permissions: account.ssh_keys:write
{ "data": { "deleted": true } }
An unknown id answers 404 SSH_KEY_NOT_FOUND.
Account
GET /me
Permissions: account:read
The account and the calling key.
account field | Type | Notes |
|---|---|---|
id | integer | |
email | string | |
firstName, lastName | string or null | |
company | boolean | the account is registered as a company |
companyName | string or null | |
country | string or null | ISO 3166-1 alpha-2 |
createdAt | integer | epoch milliseconds |
emailVerified, twoFactorEnabled | boolean |
key field | Type | Notes |
|---|---|---|
id | integer | |
label | string | |
keyPrefix | string | fpk_ + 8 characters |
scopes | array of string | the permissions it carries |
allServices | boolean | |
serviceCodes | array of string | empty when allServices is true |
ipAllowlist | array of string | empty when any address is accepted |
expiresAt | integer or null | epoch milliseconds |
spendingCapCents | integer or null | null when there is no cap |
spendingRemainingCents | integer or null | what is left in the 30-day window |
GET /me/key
No permission required. Any valid key may ask what it is.
Returns the key block alone. Useful at start-up, to check a key still carries what your integration expects.
GET /account/notifications
Permissions: account.notifications:read
| Parameter | Type | Required | Values |
|---|---|---|---|
page | integer | no, default 1 | 1 or more. 0 answers 400 VALIDATION_ERROR:page |
limit | integer | no, default 20 | 1 to 50 |
unreadOnly | string | no | true to list unread only. Any other value means false |
The envelope is not the one other lists use: no pagination block.
{ "data": { "total": 27, "unread": 4, "items": [ { "id": 167, "type": "api_key_created", "kind": "security", "read": false, "link": "/dashboard/api-keys", "createdAt": 1787906983422, "title": { "fr-fr": "Clé d'API créée", "en-us": "API key created" }, "message": { "en-us": "The key \"backup-cron\" was created on your account." } } ] } }
| Field | Type | Notes |
|---|---|---|
total | integer | the total for the filter asked: with unreadOnly=true it is the unread count |
unread | integer | always the unread count |
items[].title, items[].message | object locale → text, or null | |
items[].link | string or null | a path in the dashboard |
GET /account/access
Permissions: account.access:read
Who has delegated access to the account. Read-only: invitations are managed in the dashboard.
| Field | Type | Notes |
|---|---|---|
id | integer | |
status | string | PENDING, ACCEPTED, DECLINED, REVOKED, EXPIRED |
invitedEmail | string | the address invited |
granteeName | string or null | set once the invitation is accepted |
accountPermissions | array of string | ACCOUNT_TICKETS_VIEW, ACCOUNT_TICKETS_MANAGE |
services[] | array | serviceCode plus permissions |
services[].permissions | array of string | SERVICE_POWER, SERVICE_CONSOLE, SERVICE_FILES, SERVICE_BACKUPS, SERVICE_DATABASES, SERVICE_ALLOCATIONS, SERVICE_SCHEDULES, SERVICE_STARTUP, SERVICE_VERSIONS, SERVICE_CONTENT, SERVICE_SUBDOMAINS, SERVICE_REINSTALL, SERVICE_FIREWALL, SERVICE_RDNS, SERVICE_SFTP |
createdAt | integer | epoch milliseconds |
acceptedAt | integer or null | epoch milliseconds |
Revoked invitations are not listed.