Getting started/Jestiyon - Public API
Support
Getting started

Jestiyon - Public API

The public API for companies that integrate with Jestiyon. Use api.jestiyon.com in production and api-sandbox.jestiyon.com for testing.

Base URL

BASE URL
https://api-sandbox.jestiyon.com
Environment
Base URL
Production
https://api.jestiyon.com
Sandbox
https://api-sandbox.jestiyon.com
Getting started

Authentication

Every request carries an API key in a header.

API key

Send the API key issued to your company in the X-Api-Key header. Every endpoint except ping and webhook-keys requires a valid key; without one the API returns 401.

AUTHORIZATION
curl https://api-sandbox.jestiyon.com/api/v1/companies \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
1Sandbox and production keys are separate. A key works only in the environment it was issued for; in the other one it returns 401.
2Call GET /api/v1/me to check that a key works and to see which partner, tier and environment it belongs to.
3If your account has an IP allowlist, a request from any other address returns 403, even with a valid key.
Getting started

Getting started

Form a Turkish company: create it, submit its setup data and documents, then follow its status until formation completes, fixing anything a reviewer rejects along the way.

1. Pick a firm type

Call GET /api/v1/reference/firm-types and note the firmTypeId of the type you need. Its requiresCapital flag tells you which setup fields apply: false for a sole proprietorship (Şahıs), true for a capital company (Limited / Anonim). You send this value as firmTypeId when you create the company.

2. Create the company

POST the company title and its admin user to /api/v1/companies. The response gives you the companyId you use in every later call. The company starts with status new; taxNumber and taxOffice stay null until the tax office is assigned. The e-mail must not already belong to a Jestiyon user, or the request returns 400.

Send an Idempotency-Key header with a unique value of your own, such as a UUID or your order id. If the response never reaches you, repeat the request with the same key: you get the company the first call created instead of email_in_use.

CREATE COMPANY
curl -X POST https://api-sandbox.jestiyon.com/api/v1/companies \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-H "Idempotency-Key: 3f1c2b9e-8a4d-4f6b-9c1e-5d7a2b8c4e10" \
-H "Content-Type: application/json" \
-d '{
"title": "Örnek Yazılım Ltd. Şti.",
"firmTypeId": 2,
"firstName": "Ayşe",
"lastName": "Yılmaz",
"email": "ayse.yilmaz@ornek.com",
"mobilePrefix": "+90",
"mobilePhone": "5321234567"
}'

3. Submit setup

POST the formation data to /api/v1/companies/{companyId}/setup in one multipart request: plain form fields for the owner and capital, and lists for partners and managers. The Companies section has the full field list per firm type. The owner's identity front, identity back and address statement are required in this request, and the passport too when the owner is a foreign national (isForeignIdentity is true); the residency document is optional. The status becomes pending_document_review. To replace a single document later, use the documents endpoint with the numeric documentType id from /reference/document-types.

Under Submit setup, select Limited / Anonim to see a complete request for a company with two partners and one manager. Three things are easy to get wrong:

1A list is sent by repeating the form field once per value, for example partnerNames twice for two partners. Keep the same order in every partner* field, and likewise in every manager* field: the first value of each describes the first person.
2partnerRatios are ownership percentages and should add up to 100. Dates are sent as YYYY-MM-DD, and decimals with a dot.
3Every manager needs both sides of an identity document. Send one file per manager in each of managerIdentityFronts and managerIdentityBacks, in the same order as managerNames; a request with one missing is refused. managerResidencies is optional, but if you send it, send one per manager. Partners need no documents; if you have one to add, upload it afterwards with the documents endpoint, passing the partner's detailId from the setup response.
Upload limits
Each file may be at most 5 MB and must be a PDF, JPG, PNG or HEIC. This applies to setup and to the documents endpoint. Every file in a request is checked before any is stored, so one bad file fails the whole request with 400.

4. Poll the status

Poll GET /api/v1/companies/{companyId}/status, or subscribe to the company.status_changed webhook, until status is completed. In the sandbox nothing changes on its own: you move the company yourself, as described in Testing in the sandbox.

POLL STATUS
curl https://api-sandbox.jestiyon.com/api/v1/companies/1/status \
-H "X-Api-Key: YOUR_SANDBOX_KEY"

5. Fix a rejection

If a reviewer rejects part of the submission, status becomes rejected and rejections lists each problem with a code, the detailId of the partner or manager it concerns (null for the owner), the reason and the fix.

The code tells you what to do. These are all the codes:

Code
Concerns
How to fix
OwnerIdentityFront
The owner or the company
Upload documentType 1 (IdentityFront) with the documents endpoint, without detailId.
OwnerIdentityBack
The owner or the company
Upload documentType 2 (IdentityBack) with the documents endpoint, without detailId.
OwnerAddressStatement
The owner or the company
Upload documentType 3 (AddressStatement) with the documents endpoint, without detailId.
OwnerPassport
The owner or the company
Upload documentType 4 (Passport) with the documents endpoint, without detailId.
OwnerResidency
The owner or the company
Upload documentType 5 (Residency) with the documents endpoint, without detailId.
OwnerIdentityInfo
The owner or the company
Post setup again with these fields corrected: name, identityNo, isForeignIdentity, birthDate, birthPlace.
OwnerAddress
The owner or the company
Post setup again with these fields corrected: cityId, townId, address, addressCountry.
OwnerContact
The owner or the company
Post setup again with these fields corrected: email, phonePrefix, phone.
CapitalShares
The owner or the company
Post setup again with these fields corrected: totalCapital, shareCount, shareNominalValue, currency.
ActivityScope
The owner or the company
Post setup again with these fields corrected: activityScope, isVehicleOperation.
DetailIdentityFront
A partner or manager (detailId is set)
Upload documentType 1 (IdentityFront) with the documents endpoint, passing the rejection's detailId.
DetailIdentityBack
A partner or manager (detailId is set)
Upload documentType 2 (IdentityBack) with the documents endpoint, passing the rejection's detailId.
DetailResidency
A partner or manager (detailId is set)
Upload documentType 5 (Residency) with the documents endpoint, passing the rejection's detailId.
DetailPersonalInfo
A partner or manager (detailId is set)
Post setup again, correcting that person's value in these partner* or manager* fields: Names, BirthPlaces, BirthDates, NationalIds.
DetailContact
A partner or manager (detailId is set)
Post setup again, correcting that person's value in these partner* or manager* fields: Emails, PhonePrefixes, Phones, Addresses, AddressCountries.
DetailOwnershipRatio
A partner or manager (detailId is set)
Post setup again, correcting that person's value in these partner* or manager* fields: Ratios.
1When you post setup again, send the whole form, not only the corrected fields: every data field is overwritten with what you send. Files are the exception; one you leave out keeps the file already stored. Posting setup again resolves every open data rejection, and a document rejection only if its file is in the request.
2When the last open rejection is resolved, status returns to pending_document_review. No webhook is sent for that change, so read it from the status endpoint.
Status values
GET /api/v1/reference/statuses is the single source of truth for the status values the API and webhooks return. Optional fields that have no value yet, such as taxNumber, are returned as null, never omitted.
Getting started

Testing in the sandbox

In the sandbox you move a company through its statuses yourself, so you can test the whole flow without waiting for a reviewer.

In production a Jestiyon reviewer approves or rejects what you submit, and the back office completes the later steps. In the sandbox nothing moves on its own. Two endpoints stand in for those people. They exist only in the sandbox; in production they return 404.

Endpoint
What it does
POST /api/v1/sandbox/companies/{id}/advance
Moves the company to its next status: pending_document_review → documents_approved → e_signature_pending → e_invoice_setup → completed.
POST /api/v1/sandbox/companies/{id}/reject
Rejects the parts you name, as a reviewer would. The company becomes rejected.

Run the whole formation

1Create a company and submit setup as in Getting started. Its status is now pending_document_review.
2Call advance four times. Each call moves status one step and, if you have a webhook URL registered, sends you a company.status_changed event.
3At e_signature_pending the company is given placeholder taxNumber and taxOffice values, so you can test reading them.
ADVANCE
curl -X POST https://api-sandbox.jestiyon.com/api/v1/sandbox/companies/1/advance \
-H "X-Api-Key: YOUR_SANDBOX_KEY"

Test a rejection

1After submitting setup, call reject with one or more codes from the table in Getting started. For a code that starts with Detail, also send the detailId of the partner or manager.
2GET /api/v1/companies/{id}/status now returns rejected with your rejections, and the rejected webhook is sent.
3Fix each rejection the way the table says: upload the document, or post setup again.
4When the last one is fixed, status returns to pending_document_review and you can advance again.
REJECT
curl -X POST https://api-sandbox.jestiyon.com/api/v1/sandbox/companies/1/reject \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"rejections": [
{ "code": "OwnerIdentityFront", "reason": "Belge bulanık.", "fix": "Net bir fotoğraf yükleyin." },
{ "code": "DetailPersonalInfo", "detailId": 12 }
]
}'

Receive webhooks while testing

1Your webhook URL must be reachable from the internet. For a server on your own machine, put a tunnel such as cloudflared or ngrok in front of it and register the tunnel's address.
2Deliveries are sent by a queue, not inside your request: expect each event within about a minute of the call that caused it.
3Verify the signature against the sandbox's own keys (GET /api/v1/webhook-keys on the sandbox address), not the production ones.

Before you go live

We give you a live key after your integration has done the following in the sandbox. Every call you make there is recorded with its status and error.code, and this list is checked against that record automatically, so there is nothing to fill in or send us.

Check
How to pass it
Reference data
Call GET /api/v1/reference/firm-types.
Create a company
Create one company successfully.
Submit setup
Submit setup for it successfully.
Read the status
Call GET /api/v1/companies/{id}/status.
Fix a rejection
Reject a company with the sandbox endpoint, then upload a document or post setup again for the same company.
Recover from an error
Get a 400 from an endpoint, correct the request, and have the same endpoint succeed.
Retry a create safely
Send create company twice with the same Idempotency-Key and get the same company back.
Complete a company
Advance one company to completed.
Receive a webhook
Register a webhook URL and answer one delivery with a 2xx.

A check stays passed once you pass it. When all nine are done, write to us for your live key; if a call did not behave as you expected, include its error.request_id and we can look it up.

What to expect
A simulated step sends your webhook and nothing else; no e-mail goes to the company's admin user. Advance cannot be used while the company is rejected: fix the rejections first. reason and fix are optional, and placeholders are used when you leave them out.
Core resources

Companies

Endpoints in the Companies group.

GET/api/v1/companies

List companies

Returns every company created under your partner account, with its title, tax details and current formation status.

Parameters
ReturnsA successful response.
companyIdinteger
titlestring
taxNumberstring, nullable
taxOfficestring, nullable
statusstring
curl https://api-sandbox.jestiyon.com/api/v1/companies \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"companyId": 1,
"title": "Örnek Yazılım Ltd. Şti.",
"taxNumber": null,
"taxOffice": null,
"status": "pending_document_review"
}
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
POST/api/v1/companies

Create company

Creates a company in Turkey and its admin user under your partner account. The company starts with status new; formation data is sent separately through the setup endpoint.

Parameters
Idempotency-Keystringoptional
Header, optional. A unique value of your own for this company, up to 100 characters. Repeating the request with the same key returns the company already created instead of failing.
titlestringrequired
The company title.
firmTypeIdinteger · int32required
Company type, from GET /api/v1/reference/firm-types. It determines which setup fields are required.
firstNamestringrequired
The admin user's first name.
lastNamestringrequired
The admin user's last name.
emailstring · emailrequired
The admin user's e-mail. It must not already belong to a Jestiyon user; if it does the request returns 400.
mobilePrefixstringoptional
International dialling prefix of the mobile phone.
mobilePhonestringrequired
The admin user's mobile phone, without the prefix.
ReturnsA successful response.
companyIdinteger
titlestring
taxNumberstring, nullable
taxOfficestring, nullable
statusstring
curl -X POST https://api-sandbox.jestiyon.com/api/v1/companies \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Örnek Yazılım Ltd. Şti.",
"firmTypeId": 2,
"firstName": "Ayşe",
"lastName": "Yılmaz",
"email": "ayse.yilmaz@ornek.com",
"mobilePrefix": "+90",
"mobilePhone": "5321234567"
}'
Example response200
{
"companyId": 1,
"title": "Örnek Yazılım Ltd. Şti.",
"taxNumber": null,
"taxOffice": null,
"status": "new"
}
Example error400
{
"error": {
"code": "email_in_use",
"message": "Bu email adresi ile bir kayıt zaten var.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/companies/{id}

Get company

Returns one of your companies by its id.

Parameters
idinteger · int32required
The company id (companyId).
ReturnsA successful response.
companyIdinteger
titlestring
taxNumberstring, nullable
taxOfficestring, nullable
statusstring
curl https://api-sandbox.jestiyon.com/api/v1/companies/1 \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
{
"companyId": 1,
"title": "Örnek Yazılım Ltd. Şti.",
"taxNumber": null,
"taxOffice": null,
"status": "pending_document_review"
}
Example error404
{
"error": {
"code": "not_found",
"message": "Şirket bulunamadı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/companies/{id}/status

Company status

Returns a company's current formation status, whether setup has been submitted, and any open rejections. Each rejection has a code, the detailId of the partner or manager it concerns (null for the owner), the reason and the fix. rejections is always an array; it is empty when nothing is rejected.

Parameters
idinteger · int32required
The company id (companyId).
ReturnsA successful response.
companyIdinteger
statusstring
isSetupSubmittedboolean
rejectionsarray
Each item
codestring
detailIdinteger, nullable
partstring
reasonstring, nullable
fixstring, nullable
curl https://api-sandbox.jestiyon.com/api/v1/companies/1/status \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
{
"companyId": 1,
"status": "rejected",
"isSetupSubmitted": true,
"rejections": [
{
"code": "DetailIdentityFront",
"detailId": 12,
"part": "Ortak/Yönetici Kimlik Ön Yüz",
"reason": "Belge bulanık, okunamıyor.",
"fix": "Kimliğin ön yüzünün net bir fotoğrafını yeniden yükleyin."
},
{
"code": "OwnerContact",
"detailId": null,
"part": "İletişim Bilgileri",
"reason": "Telefon numarası eksik haneli.",
"fix": "Geçerli bir cep telefonu numarası gönderin."
}
]
}
Example error404
{
"error": {
"code": "not_found",
"message": "Şirket bulunamadı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
POST/api/v1/companies/{id}/setup

Submit setup

Submits the formation data and documents in one multipart request: plain form fields for the owner and capital, lists for partners and managers, and the required document files. Use the firm-type switch above the parameters: the required fields and the sample request change with it. The Limited / Anonim sample is a company with two partners and one manager; a sole proprietorship sends only the owner fields. Setup is accepted once. While the company is rejected you may post it again with the whole form: the existing setup is updated, partner and manager ids stay the same, and a file you leave out keeps the one already stored.

Parameters
idinteger · int32required
The company id (companyId).
ReturnsA successful response.
companyIdinteger
statusstring
detailsarray
Each item
detailIdinteger
typestring
namestring
curl -X POST https://api-sandbox.jestiyon.com/api/v1/companies/1/setup \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-F "name=Ayşe Yılmaz" \
-F "identityNo=10000000146" \
-F "isForeignIdentity=false" \
-F "birthDate=1990-05-14" \
-F "birthPlace=İstanbul" \
-F "cityId=34" \
-F "townId=1234" \
-F "address=Bağdat Cad. No:1, Kadıköy" \
-F "addressCountry=TR" \
-F "email=ayse.yilmaz@ornek.com" \
-F "phonePrefix=+90" \
-F "phone=5321234567" \
-F "isVehicleOperation=false" \
-F "ownerIdentityFront=@/path/to/ownerIdentityFront" \
-F "ownerIdentityBack=@/path/to/ownerIdentityBack" \
-F "ownerAddressStatement=@/path/to/ownerAddressStatement" \
-F "activityScope=Yazılım geliştirme ve danışmanlık"
Example response200
{
"companyId": 1,
"status": "pending_document_review",
"details": []
}
Example error400
{
"error": {
"code": "document_missing",
"message": "ownerAddressStatement: şirket sahibinin adres beyanı zorunludur.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
POST/api/v1/companies/{id}/documents

Upload document

Uploads one formation document for the owner, or for a partner or manager. Setup must have been submitted first. The file replaces the one already stored for that document, and if that document was rejected the rejection is resolved. When a company's last open rejection is resolved its status returns to pending_document_review.

Parameters
idinteger · int32required
The company id (companyId).
documentTypeinteger · int32required
Which document this is, as the numeric id from GET /api/v1/reference/document-types (for example 1 = IdentityFront). The owner accepts every type; a partner or manager accepts identity front, identity back and residency.
detailIdinteger · int32optional
The partner or manager the document belongs to, from the setup response. Leave it out to upload for the owner.
filefilerequired
The document file. Max 5 MB; PDF, JPG, PNG or HEIC.
ReturnsA successful response.
documentIdinteger
documentTypestring
detailIdinteger, nullable
fileNamestring
curl -X POST https://api-sandbox.jestiyon.com/api/v1/companies/1/documents \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-F "documentType=1" \
-F "detailId=12" \
-F "file=@/path/to/file"
Example response200
{
"documentId": 5001,
"documentType": "IdentityFront",
"detailId": 12,
"fileName": "identity-front.jpg"
}
Example error400
{
"error": {
"code": "file_too_large",
"message": "file: dosya boyutu 5 MB sınırını aşıyor.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Core resources

Me

Endpoints in the Me group.

GET/api/v1/me

Current partner

Returns the partner the API key belongs to: its id, name, tier and the environment of the key.

Parameters
ReturnsThe partner the key belongs to.
partnerIdinteger
namestring
tierstring
environmentstring
curl https://api-sandbox.jestiyon.com/api/v1/me \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
{
"partnerId": 1,
"name": "Örnek Entegrasyon A.Ş.",
"tier": "standard",
"environment": "sandbox"
}
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Core resources

Ping

Endpoints in the Ping group.

GET/api/v1/ping

Ping

Returns a small response showing the API is up. It needs no API key and is safe to use for uptime checks.

Parameters
ReturnsThe API is reachable.
statusstring
versionstring
environmentstring
serverTimeUtctimestamp
curl https://api-sandbox.jestiyon.com/api/v1/ping
Example response200
{
"status": "ok",
"version": "1",
"environment": "sandbox",
"serverTimeUtc": "2026-08-12T09:30:00Z"
}
Example error429
{
"error": {
"code": "rate_limit_exceeded",
"message": "Fazla istek gönderildi. Daha sonra tekrar deneyin.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Core resources

Reference

Endpoints in the Reference group.

GET/api/v1/reference/firm-types

Firm types

Returns the company types that can be formed in Turkey, and whether each requires capital.

Parameters
ReturnsA successful response.
firmTypeIdinteger
namestring
descriptionstring
countryIdinteger
requiresCapitalboolean
curl https://api-sandbox.jestiyon.com/api/v1/reference/firm-types \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"firmTypeId": 1,
"name": "Şahıs Şirketi",
"description": "Tek kişilik işletme",
"countryId": 1,
"requiresCapital": false
},
{
"firmTypeId": 2,
"name": "Limited Şirket",
"description": "Sermaye şirketi (Ltd. Şti.)",
"countryId": 1,
"requiresCapital": true
}
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/reference/currencies

Currencies

Returns the available currencies.

Parameters
ReturnsA successful response.
idinteger
namestring
curl https://api-sandbox.jestiyon.com/api/v1/reference/currencies \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"id": 1,
"name": "TRY"
},
{
"id": 2,
"name": "USD"
}
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/reference/cities

Cities

Returns the cities (il) of Turkey.

Parameters
ReturnsA successful response.
idinteger
namestring
curl https://api-sandbox.jestiyon.com/api/v1/reference/cities \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"id": 34,
"name": "İstanbul"
},
{
"id": 6,
"name": "Ankara"
}
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/reference/towns

Towns

Returns the towns (ilçe) of a city.

Parameters
cityIdinteger · int32required
The city id, from GET /api/v1/reference/cities.
ReturnsA successful response.
idinteger
namestring
curl https://api-sandbox.jestiyon.com/api/v1/reference/towns?cityId=1 \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"id": 1234,
"name": "Kadıköy"
},
{
"id": 1235,
"name": "Beşiktaş"
}
]
Example error400
{
"error": {
"code": "invalid_request",
"message": "Geçersiz istek.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/reference/document-types

Document types

Returns the document types accepted for formation. Send the id as documentType when uploading a document.

Parameters
ReturnsA successful response.
idinteger
namestring
curl https://api-sandbox.jestiyon.com/api/v1/reference/document-types \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
{
"id": 1,
"name": "IdentityFront"
},
{
"id": 2,
"name": "IdentityBack"
},
{
"id": 3,
"name": "AddressStatement"
},
{
"id": 4,
"name": "Passport"
},
{
"id": 5,
"name": "Residency"
}
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
GET/api/v1/reference/statuses

Statuses

Returns every formation status the API can return. A company starts as new and moves to pending_document_review once setup is submitted.

Parameters
ReturnsA successful response.
curl https://api-sandbox.jestiyon.com/api/v1/reference/statuses \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
[
"new",
"pending_document_review",
"documents_approved",
"e_signature_pending",
"e_invoice_setup",
"completed",
"rejected"
]
Example error401
{
"error": {
"code": "unauthorized",
"message": "Geçersiz veya eksik API anahtarı.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Core resources

Sandbox

Endpoints in the Sandbox group.

POST/api/v1/sandbox/companies/{id}/advance

Advance company (sandbox)

Sandbox only; returns 404 in production. Moves one of your companies to its next formation status, as the reviewer or back office would: pending_document_review → documents_approved → e_signature_pending → e_invoice_setup → completed. It sends the same company.status_changed webhook as production. At e_signature_pending the company is given placeholder taxNumber and taxOffice values. It cannot be used before setup is submitted, while the company is rejected, or after completed.

Parameters
idinteger · int32required
The company id (companyId).
ReturnsA successful response.
companyIdinteger
statusstring
isSetupSubmittedboolean
rejectionsarray
Each item
codestring
detailIdinteger, nullable
partstring
reasonstring, nullable
fixstring, nullable
curl -X POST https://api-sandbox.jestiyon.com/api/v1/sandbox/companies/1/advance \
-H "X-Api-Key: YOUR_SANDBOX_KEY"
Example response200
{
"companyId": 1,
"status": "documents_approved",
"isSetupSubmitted": true,
"rejections": []
}
Example error400
{
"error": {
"code": "invalid_company_status",
"message": "Şirket reddedilmiş durumda. Önce açık retleri düzeltin; durum yeniden incelemeye döndüğünde ilerletebilirsiniz.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
POST/api/v1/sandbox/companies/{id}/reject

Reject company (sandbox)

Sandbox only; returns 404 in production. Rejects the parts of a company's setup that you name, as a reviewer would. The company becomes rejected, the rejections appear on the status endpoint, and the same company.status_changed webhook as production is sent. Setup must have been submitted first. Calling it again replaces any rejections that are still open.

Parameters
idinteger · int32required
The company id (companyId).
rejectionsarrayoptional
The parts to reject, at least one. Each has a code from the rejection code table, a detailId for the codes that start with Detail, and optionally a reason and a fix.
ReturnsA successful response.
companyIdinteger
statusstring
isSetupSubmittedboolean
rejectionsarray
Each item
codestring
detailIdinteger, nullable
partstring
reasonstring, nullable
fixstring, nullable
curl -X POST https://api-sandbox.jestiyon.com/api/v1/sandbox/companies/1/reject \
-H "X-Api-Key: YOUR_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"rejections": [
{
"code": "OwnerIdentityFront",
"detailId": null,
"reason": "Belge bulanık, okunamıyor.",
"fix": "Kimliğin ön yüzünün net bir fotoğrafını yeniden yükleyin."
}
]
}'
Example response200
{
"companyId": 1,
"status": "rejected",
"isSetupSubmitted": true,
"rejections": [
{
"code": "OwnerIdentityFront",
"detailId": null,
"part": "Kimlik Ön Yüz",
"reason": "Belge bulanık, okunamıyor.",
"fix": "Kimliğin ön yüzünün net bir fotoğrafını yeniden yükleyin."
}
]
}
Example error400
{
"error": {
"code": "invalid_rejection_code",
"message": "Geçersiz ret kodu: IdentityFront",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Core resources

Webhook keys

Endpoints in the Webhook keys group.

GET/api/v1/webhook-keys

Webhook keys

Returns the public keys used to verify webhook signatures, as a JWKS. Use the key whose kid matches the one in a delivery's X-Signature header. It needs no API key. Cache the result and fetch again only when you see a kid you do not know.

Parameters
ReturnsA JSON Web Key Set with one or more EC P-256 public keys.
keysarray
Each item
ktystring
crvstring
usestring
algstring
kidstring
xstring
ystring
curl https://api-sandbox.jestiyon.com/api/v1/webhook-keys
Example response200
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"use": "sig",
"alg": "ES256",
"kid": "a1b2c3d4e5",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uZfa4lYs",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
}
]
}
Example error429
{
"error": {
"code": "rate_limit_exceeded",
"message": "Fazla istek gönderildi. Daha sonra tekrar deneyin.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
Try itapi-sandbox.jestiyon.com
Simulated response, no data is written.
Events

Webhooks

Jestiyon posts signed events to your webhook URL when a company's formation status changes.

Jestiyon sends a company.status_changed event to your webhook URL each time a company's formation status changes. Each delivery is signed with ES256 (ECDSA P-256); you verify it with Jestiyon's public keys, so no secret is exchanged. Return 2xx within 15 seconds to acknowledge. Anything else is retried with exponential backoff, up to 6 attempts in total.

Registering a webhook
Webhook delivery is set up per partner account. Contact the Jestiyon integration team to register your URL; there is no self-serve endpoint yet.

Verifying deliveries

Header
Description
X-Signature
ES256 signature of {t}.{rawBody}, in the form t=<unix>,kid=<key-id>,v1=<base64url>.
X-Webhook-Event
The event type, for example company.status_changed.
X-Webhook-Id
An id for the event that stays the same across retries. Use it to ignore duplicates.
1Read t, kid and v1 from X-Signature.
2Fetch the public key with that kid from GET /api/v1/webhook-keys (JWKS). Cache it, and fetch again only when you see a kid you do not know.
3Verify the ES256 signature v1 over the bytes of {t}.{rawBody}. Use the raw request body, before any JSON parsing or re-serialization.
4Reject the delivery if t is too old (for example more than 300 seconds), so a captured request cannot be replayed later.
5Skip the event if you have already processed its X-Webhook-Id: an event can be delivered more than once.
JWKS — GET /api/v1/webhook-keys
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"use": "sig",
"alg": "ES256",
"kid": "a1b2c3d4e5",
"x": "f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uZfa4lYs",
"y": "x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"
}
]
}
import base64, time
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
from cryptography.hazmat.primitives import hashes
def b64url(s): return base64.urlsafe_b64decode(s + '=' * (-len(s) % 4))
def verify(jwk, headers, raw_body, tolerance=300):
p = dict(kv.split('=', 1) for kv in headers['X-Signature'].split(',') if '=' in kv)
t, v1 = p['t'], p['v1']
x = int.from_bytes(b64url(jwk['x']), 'big')
y = int.from_bytes(b64url(jwk['y']), 'big')
key = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
sig = b64url(v1) # P-1363 r|s -> DER
der = encode_dss_signature(int.from_bytes(sig[:32], 'big'),
int.from_bytes(sig[32:], 'big'))
key.verify(der, f'{t}.'.encode() + raw_body, ec.ECDSA(hashes.SHA256())) # raises if invalid
assert abs(time.time() - int(t)) <= tolerance # freshness / replay

Status values

Each company.status_changed event carries one of the status values below. No event is sent when a company enters pending_document_review, whether after setup or after a rejection is fixed.

Status
When it is sent
documents_approved
The accountant approved the uploaded documents.
e_signature_pending
The formation steps are complete and the tax office is assigned.
e_invoice_setup
The e-signature step is complete and the e-invoice integrator is being set up.
completed
The company is fully formed.
rejected
Part of the submission was rejected. rejections lists each part with its reason and fix; call the status endpoint for the code and detailId of each.

Example payload

rejections is always present. It is an empty array unless status is rejected.

WEBHOOK PAYLOAD
{
"event": "company.status_changed",
"companyId": 1,
"status": "documents_approved",
"rejections": [],
"occurredAtUtc": "2026-08-12T09:30:00Z"
}
Resources

Errors

Every error has the same JSON shape. Use error.code in your code; error.message is for people.

ERROR ENVELOPE
{
"error": {
"code": "invalid_request",
"message": "Email gerekli. Şirket adını girin.",
"request_id": "00-6adb6bbc32f4eeed793ff1e8b33db300-7dfeb60b0069031f-01"
}
}
1error.code is stable: write your error handling against it. error.message is meant for people, is in Turkish, and may change.
2There is no per-field breakdown: the message describes what is wrong, and several problems found together are joined into the one message.
3error.request_id identifies the request. Include it when you contact support.

Error codes

A failed validation is invalid_request unless it has one of the more specific codes below. New codes may be added, so treat an unknown code like invalid_request.

Status
error.code
Meaning
400
invalid_request
A field is missing or invalid, with no more specific code below.
400
email_in_use
The e-mail already belongs to a Jestiyon user. Use a different one.
400
invalid_firm_type
The firmTypeId is not one of the firm types the API offers.
400
invalid_identity_number
A TC identity number failed its checksum.
400
setup_already_submitted
Setup was already submitted and the company is not rejected, so it cannot be sent again.
400
setup_not_submitted
The request needs setup to have been submitted first.
400
document_missing
A required document file was not sent.
400
file_too_large
A file is larger than 5 MB.
400
unsupported_file_type
A file is not a PDF, JPG, PNG or HEIC.
400
invalid_document_type
The documentType is unknown, or not accepted for a partner or manager.
400
invalid_company_status
The company's current status does not allow the request.
400
invalid_rejection_code
Sandbox only: a rejection code is not one of the published codes.
409
idempotency_key_reused
The Idempotency-Key was already used to create a different company. Send a new key.
401
unauthorized
The API key is missing, invalid, expired, or issued for the other environment.
403
forbidden
The key is valid but the request came from an address outside your IP allowlist.
404
not_found
No resource with that id under your account.
429
rate_limit_exceeded
Rate limit exceeded. See the Retry-After header.
500
internal_error
Something failed on our side. Retry later.

Retrying safely

A timeout or a dropped connection does not tell you whether a request went through. GET requests and document uploads can simply be repeated; a second upload replaces the first. A repeated setup returns setup_already_submitted, which means the first one arrived. Create company is the one call that needs help: send it with an Idempotency-Key header and repeat it with the same key.

1The same key with the same title, firmTypeId and email returns 200 with the company already created. Its status is the current one, so it may no longer be new.
2The same key with a different title, firmTypeId or email returns 409 idempotency_key_reused.
3A key is up to 100 characters, belongs to your account only, and does not expire.

Rate limits

When you exceed your rate limit the API returns 429 with a Retry-After header giving the number of seconds to wait. Retrying sooner fails again. Your limit depends on your tier, which GET /api/v1/me returns.

Null vs. omitted
An optional field with no value is returned as null; it is never left out of the response. Check for null, not for a missing key.
Resources

Changelog

Notable changes to the public API.

v1.0 — October 2026

1Rejections can now be fixed through the API: uploading a document resolves the rejection on that document, and posting setup again while rejected updates the existing setup.
2Each rejection returned by the status endpoint has a code and a detailId, so you can tell what to fix.
3Uploads are limited to 5 MB per file and to PDF, JPG, PNG or HEIC, for both setup and documents.
4Setup requires the owner's identity front, identity back and address statement (and the passport for a foreign owner), and both sides of an identity document for every manager. A request with one missing is refused.
5Create company accepts an Idempotency-Key header, so a request whose response was lost can be repeated safely.
6Create company refuses an e-mail that already belongs to a Jestiyon user.
7Errors have more specific codes: a taken e-mail is email_in_use, a missing document is document_missing, and so on. Failures without a specific code are still invalid_request.
8Sandbox: two endpoints let you advance a company to its next status or reject parts of its setup, so the whole flow can be tested without waiting for a reviewer.
9An unknown documentType on upload returns 400 invalid_document_type; it used to be accepted and ignored.
10Webhooks: the t value in X-Signature and occurredAtUtc in the payload are now true UTC. They were three hours ahead, which made a freshness check reject every delivery.
11Sandbox calls are recorded and checked against a going-live list before a live key is issued. See Testing in the sandbox.

v1.0 — August 2026

1First public release: Companies (create, get, list, setup, documents, status), Me, Ping and the Reference lookups.
2Webhooks: signed company.status_changed events (ES256) when the formation status changes.
3Request and response fields use camelCase, for example firmTypeId; the error envelope's request_id is the one exception.
Resources

Support

Get help from the Jestiyon integration team.

contact
Jestiyon Integration Team
Reach out for API access, sandbox keys and integration questions.
integrations@jestiyon.com
website
Website
Product information and company details.
www.jestiyon.com
Open a ticket
Routed to the Jestiyon integration team.