Onboarding API: endpoints¶
| Base URL | Sandbox https://provider-dev.timologisi.online · Production https://provider.timologisi.online |
| Authentication | Header API-KEY, the same key as for the Provider API. The key identifies which software house is calling. Never send it in the body or the URL. |
| Limit | 60 calls / minute per key. The X-RateLimit-Remaining header shows how many remain. Above the limit: 429 RATE_LIMITED with Retry-After. |
| Dates | Date-time in ISO-8601 UTC. Plain date as YYYY-MM-DD. |
| Files | OpenAPI (YAML) · Postman |
| Endpoint | What it does | |
|---|---|---|
| POST | /api/v1/requests |
Create a request for one client |
| GET | /api/v1/requests |
List your requests |
| GET | /api/v1/requests/{requestId} |
Request status |
| GET | /api/v1/requests/{requestId}/history |
Change history |
| GET | /api/v1/requests/{requestId}/contract |
Download the contract (PDF) |
| POST | /api/v1/requests/{requestId}/signed-contract |
Upload the signed contract |
| DELETE | /api/v1/requests/{requestId} |
Cancel a request |
| POST GET DELETE | /api/v1/webhooks… |
Webhooks |
Response format¶
All JSON endpoints wrap their response in an envelope:
The error object may also contain context (e.g. existingRequestId, currentStatus) and, only on 500, traceId.
Create a request¶
POST /api/v1/requests
| Header | |
|---|---|
Idempotency-Key |
Optional. A GUID of your own, one per client, unchanged across all retries. If the response is lost and you resend with the same key, you get back the original request (200 instead of 201). Do not reuse it for another VAT number, or for a request that was cancelled or rejected: 409. |
{
"companyDetails": {
"legalName": "Παπαδόπουλος Α.Ε.",
"tradeName": "PapaCorp",
"vatNumber": "094019245",
"taxOffice": "Α΄ Αθηνών",
"transactionTypes": [
"B2B",
"B2C"
]
},
"address": {
"city": "Αθήνα",
"streetAddress": "Ερμού 15",
"postalCode": "10563"
},
"contactInfo": {
"email": "info@papacorp.gr",
"phone": "2101234567",
"backupPhone": "6971234567"
},
"administrator": {
"fullName": "Γιάννης Παπαδόπουλος",
"vatNumber": "090000045"
},
"ispDetails": {
"providerName": "Cosmote",
"contractNumber": "CSM-2026-1",
"contractDate": "2026-09-01"
}
}
| Field | Required | Rules |
|---|---|---|
companyDetails.legalName |
yes | |
companyDetails.tradeName |
||
companyDetails.vatNumber |
yes | 9 digits with a valid check digit. Test values such as 123456789 are rejected. |
companyDetails.taxOffice |
yes | |
companyDetails.transactionTypes |
yes | At least one of B2B, B2C. B2G is not accepted. |
address.city, address.streetAddress |
yes | |
address.postalCode |
||
contactInfo.email |
yes | The contract is sent here. |
contactInfo.phone |
yes | E.164 (+30…) or a Greek 10-digit number |
contactInfo.backupPhone |
Same format | |
administrator.fullName |
yes | The legal representative |
administrator.vatNumber |
yes | A valid VAT number (check digit) |
ispDetails.providerName, contractNumber, contractDate |
if B2C | The client's internet service provider, all three fields. contractDate cannot be in the future. If B2B only, omit ispDetails. |
All field errors are returned together in error.details[].
The system decides the request type. You do not send it:
NEW_CONTRACT: the VAT number has no active contract with Novus. A PDF contract is generated and the request goes toPENDING_SIGNATURE.LINK_EXISTING: the VAT number already has an active contract through another software house.contractisnull, no signature is needed, and the request goes straight toUNDER_REVIEW.
The client automatically receives an email with the contract and a link to upload it themselves. So the request can move to UNDER_REVIEW without any action on your side.
| HTTP | When |
|---|---|
201 |
The request was created. |
200 |
Retry with the same Idempotency-Key: the original request is returned. |
409 |
DUPLICATE_OPEN_REQUEST: an open request already exists for the VAT number, and error.context.existingRequestId shows which one. CLIENT_ALREADY_LINKED: the VAT number is already active on your key. Or an Idempotency-Key was reused for another VAT number / for a cancelled or rejected request. |
422 |
VALIDATION_ERROR: see details[]. |
{
"success": true,
"data": {
"requestId": "req_8f7b2c9a",
"requestType": "NEW_CONTRACT",
"status": "PENDING_SIGNATURE",
"message": "Η αίτηση καταχωρήθηκε. Απαιτείται υπογραφή της σύμβασης από τον πελάτη.",
"contract": {
"contractNumber": "10000",
"contractDate": "2026-09-17",
"templateVersion": "v6",
"downloadUrl": "https://provider.timologisi.online/api/v1/requests/req_8f7b2c9a/contract"
},
"provisioning": null,
"aadeStatement": null,
"createdAt": "2026-09-17T08:42:00Z"
}
}
{
"success": true,
"data": {
"requestId": "req_3a1c7d02",
"requestType": "LINK_EXISTING",
"status": "UNDER_REVIEW",
"message": "Το ΑΦΜ έχει ήδη ενεργή σύμβαση. Η αίτηση σύνδεσης εστάλη για έλεγχο.",
"contract": null,
"provisioning": null,
"aadeStatement": null,
"createdAt": "2026-09-17T08:42:00Z"
}
}
List requests¶
GET /api/v1/requests?status=UNDER_REVIEW,ACTION_REQUIRED&page=1&pageSize=50
Returns only your own requests.
| Query | Description |
|---|---|
status |
One or more statuses, separated by commas |
requestType |
NEW_CONTRACT or LINK_EXISTING |
vatNumber |
Client VAT number |
createdFrom, createdTo |
Date range (YYYY-MM-DD) |
page |
From 1 (default 1) |
pageSize |
1 to 200 (default 50) |
sort |
createdAt:desc (default), createdAt:asc, updatedAt:desc, updatedAt:asc |
data is { "items": [ … ], "total": 137, "page": 1, "pageSize": 50 }.
Request status¶
GET /api/v1/requests/{requestId}
The actual status of the request. After APPROVED, provisioning and aadeStatement are filled in.
| Field | Meaning |
|---|---|
status |
See the statuses. |
message |
The latest message, e.g. why the request is in ACTION_REQUIRED. |
contract |
Number, date, templateVersion, downloadUrl. After an upload, also signedVersion (increases with each upload) and sha256. In LINK_EXISTING it is null, and this is not an error. |
provisioning |
After APPROVED. status: PENDING → IN_PROGRESS → COMPLETED or FAILED. Steps clientAdded, clientLinked, contractUploaded, statementSent, each PENDING, IN_PROGRESS, DONE or FAILED. Plus error. |
aadeStatement |
The Provider declaration to AADE (the Greek tax authority). status: SUBMITTED, ACCEPTED (the client accepted it), PRESUMED_ACCEPTED (10 days passed with no action), RECALLED. Plus acceptDate. |
When the client can start issuing
When provisioning.status = COMPLETED.
If you see FAILED, Novus has already been notified and is handling it. Do not send the request again.
Example: approved and activated request
{
"success": true,
"data": {
"requestId": "req_8f7b2c9a",
"requestType": "NEW_CONTRACT",
"status": "APPROVED",
"message": "Η αίτηση εγκρίθηκε.",
"contract": {
"contractNumber": "10000",
"contractDate": "2026-09-17",
"templateVersion": "v6",
"signedVersion": 1,
"sha256": "9f2c1d6e8a0b4c7d5e3f1a2b8c9d0e4f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d",
"downloadUrl": "https://provider.timologisi.online/api/v1/requests/req_8f7b2c9a/contract"
},
"provisioning": {
"status": "COMPLETED",
"clientAdded": "DONE",
"clientLinked": "DONE",
"contractUploaded": "DONE",
"statementSent": "DONE",
"error": null
},
"aadeStatement": { "status": "SUBMITTED", "acceptDate": null },
"createdAt": "2026-09-17T08:42:00Z",
"updatedAt": "2026-09-17T11:03:00Z"
}
}
Change history¶
GET /api/v1/requests/{requestId}/history
A chronological list of changes. It is useful, for example, when the client uploaded the contract themselves. Each entry has:
| Field | |
|---|---|
occurredAt |
When |
status, previousStatus |
From which status to which |
message |
Message |
actor |
Who caused the change: SOFTWARE_HOUSE, CLIENT, NOVUS, SYSTEM |
Download the contract¶
GET /api/v1/requests/{requestId}/contract?kind=unsigned
Returns application/pdf.
| Query | |
|---|---|
kind |
unsigned (default) or signed |
version |
Version of the signed file. Without it, the latest is returned. |
Not a public link
It needs your API key. Do not send the downloadUrl to the client as is. Download the PDF and forward it yourself.
A LINK_EXISTING request has no contract: 409 NOT_APPLICABLE. The same applies when the version you asked for does not exist.
Upload the signed contract¶
POST /api/v1/requests/{requestId}/signed-contract
multipart/form-data, one file in the contractFile field.
curl -X POST "https://provider-dev.timologisi.online/api/v1/requests/req_8f7b2c9a/signed-contract" \
-H "API-KEY: $NOVUS_API_KEY" \
-F "contractFile=@signed-contract.pdf;type=application/pdf"
Rules:
- exactly one file,
- a real PDF (the magic bytes are checked, not the extension), up to 15 MB, not corrupted or password-protected,
- the request is in
PENDING_SIGNATUREorACTION_REQUIRED, withrequestType = NEW_CONTRACT.
Old files are not deleted. Each upload is a new version. On success (200) the request moves to UNDER_REVIEW.
| HTTP | error.code |
When |
|---|---|---|
400 |
BAD_REQUEST |
Invalid multipart, more than one file, or a corrupted / password-protected PDF |
409 |
INVALID_TRANSITION |
Wrong status, e.g. a file is already under review |
409 |
NOT_APPLICABLE |
The request is LINK_EXISTING |
413 |
FILE_TOO_LARGE |
Over 15 MB |
415 |
UNSUPPORTED_FILE_TYPE |
Not a PDF |
The client can also upload the file themselves, from the link in the email.
Cancel a request¶
DELETE /api/v1/requests/{requestId}
Optional body { "reason": "…" } (up to 500 characters). Allowed only before approval. On a request that is already approved or in a final status: 409 INVALID_TRANSITION. Ending the cooperation after approval is possible only in agreement with Novus, not through the API.
Error codes¶
| HTTP | error.code |
Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
Malformed JSON or multipart |
| 401 | UNAUTHORIZED |
The API key is missing or invalid |
| 403 | FORBIDDEN |
The action is not allowed with this key |
| 404 | NOT_FOUND |
It does not exist or belongs to another software house. 404 is returned instead of 403, so that its existence is not revealed. |
| 409 | DUPLICATE_OPEN_REQUEST |
An open request already exists for the VAT number (context.existingRequestId) |
| 409 | CLIENT_ALREADY_LINKED |
The VAT number is already active on your key |
| 409 | CLIENT_ALREADY_ACTIVE |
The VAT number got an active contract before approval |
| 409 | INVALID_TRANSITION |
Not allowed in the current status (context.currentStatus) |
| 409 | NOT_APPLICABLE |
Does not apply to this request type |
| 413 | FILE_TOO_LARGE |
File over 15 MB |
| 415 | UNSUPPORTED_FILE_TYPE |
Not a PDF |
| 422 | VALIDATION_ERROR |
Invalid fields, see details[] |
| 429 | RATE_LIMITED |
More than 60 calls / minute. Retry after Retry-After seconds. |
| 500 | INTERNAL_ERROR |
Unexpected error, with traceId. Send us the traceId. |
| 502 | UPSTREAM_ERROR |
Temporary failure of an external service. Retry later. |