API v1

The Ponyglot Cloud API connects a website (through a connector) with Ponyglot. This page lists every endpoint of version 1 with its fields and status codes. The interactive reference shows the same from the machine-readable schema.

Basics

Base URL

https://api.ponyglot.app/v1/. The address is permanent. Changes to v1 stay backwards compatible: new optional fields and endpoints may appear, existing ones keep working.

Authentication

Authorization: Bearer <API key>. Keys start with pg_ and are created per site in the dashboard. A key belongs to exactly one site, and every request acts on that site.

Format

JSON in UTF-8, for requests and responses. Send Content-Type: application/json.

Times

ISO 8601 with time zone, for example 2026-09-24T10:00:00Z.

Status codes

Code

Meaning

200

OK.

202

Job accepted (POST /jobs).

204

Unit deleted (DELETE /units/{external_key}).

401

Missing, invalid or revoked API key.

402

No subscription covers the site (POST /jobs). See Billing errors.

404

Unknown unit or job.

409

The job needs confirmation (POST /jobs). See Billing errors.

422

The request is invalid. detail is a message, or a list of validation errors that name the invalid fields.

Limits

What

Limit

Request body

10 MB

Units per batch (PUT /units/batch)

1–100

Segments per unit

5,000

Characters per segment text

100,000

external_key

^[A-Za-z0-9_.:-]{1,255}$

Segment key, parent_key

1–500 characters (any characters)

Language codes

2–15 characters: letters, digits, -, _

Units per job (units)

1,000

Results per page (limit)

1–500, default 100

IDs per acknowledgement

1–1,000

Reviews per request

1–500

Site

GET /site

The site the API key belongs to. Use it to check a key.

Field

Type

Description

id

integer

The site’s ID.

name

string

The site’s name.

organization

string

The organization’s name.

source_language

string

Source language from the site settings.

target_languages

list

One entry per target language: code, formality (default, formal, informal), engine (auto, deepl, llm), writable (boolean).

enabled_adapters

list of strings

Adapters enabled in the settings: djangocms, wagtail, parler, modeltranslation.

enabled_engines

list of strings

Engines content may be sent to: deepl, llm.

POST /handshake

The connector reports itself. Send it on start-up and when settings change.

Request field

Type

Description

connector_version

string, required

Up to 50 characters, e.g. 0.1.0.

adapters

list of strings

The connector’s active adapters, up to 20.

source_language

language code, required

The website’s source language.

languages

list of language codes

All languages configured on the website (e.g. settings.LANGUAGES), up to 200.

The response is the site, as in GET /site, plus:

Field

Type

Description

review_mode

string

Always always: every translation is reviewed by an editor.

warnings

list of strings

Mismatches, e.g. a different source language, or target languages missing from languages.

In the response, a target language is writable only if it is writable in the site settings and listed in languages.

GET /status

Counts for the whole site. With prefix, only for the units whose external_key starts with it, for example parler:blog.post: for all objects of one model.

Field

Type

Description

units_total

integer

Live units.

segments_total

integer

Live segments.

glossary_terms

integer

Terms in the organization’s glossary.

languages

list

Per target language: code, ok, stale, review, missing, attention (see Translation states, statuses and origins).

GET /usage

This month’s billable characters for the site’s usage scope, for example for a connector’s toolbar.

Field

Type

Description

plan

string or null

Plan code: pro, agency, growth, scale; null without a subscription.

entitled

boolean

Whether hosted translation is available for this site.

reason

string

Why not, if entitled is false.

month_start, month_end

time

The usage month: a calendar month in UTC.

included_characters

integer or null

Included per month; null if not metered.

used_characters

integer

Billable characters used this month in the scope.

per_site

boolean

true for Pro (counted per site); false for pooled plans.

GET /health

Returns {"status": "ok", "api_version": "1"}. Needs no API key.

Units

PUT /units

Push the complete current state of one unit. See Units, segments, formats and kinds for keys, formats and kinds.

Request field

Type

Description

external_key

string, required

Stable ID of the object, chosen by the connector: ^[A-Za-z0-9_.:-]{1,255}$, e.g. djangocms:page:42.

adapter

string, required

djangocms, wagtail, parler or modeltranslation.

label

string

Human-readable title for the dashboard, up to 500 characters.

path

string

URL path on the site, e.g. /pricing/, up to 2,000 characters.

source_language

language code

The unit’s source language, if it differs from the site’s.

writable_languages

list of language codes or null

Restrict the target languages of this unit; null means all.

metadata

object

Anything useful for the connector, e.g. model or template names.

segments

list, required

All current segments, up to 5,000. An empty list removes all segments.

Segment fields:

Field

Type

Description

key

string, required

Stable and unique within the unit, up to 500 characters.

text

string, required

Source text, up to 100,000 characters.

format

string

plain (default), html, markdown, rich_text.

field

string

Name of the model field, up to 200 characters.

kind

string

Hint for QA and prompts, e.g. title, body, alt_text, meta_description, link_text; up to 50 characters.

max_length

integer

Maximum length of the target field, if it has one.

position

integer

Order within the parent; defaults to the order in the list.

parent_key

string

Key of the parent element, e.g. the plugin that contains this one.

fingerprint

string

Optional; your own fingerprint of text (see Fingerprints).

translations

object

Optional existing translations: language code → text (see Import existing translations).

Response 200:

Field

Type

Description

external_key

string

The unit.

unit_created

boolean

true if Ponyglot didn’t know the unit before. A deleted unit that comes back counts as known.

added

integer

New segment keys.

changed

integer

Segments whose text changed.

removed

integer

Segment keys that are no longer present.

unchanged

integer

Segments with the same text.

stale_translations

integer

Translations that became stale with this push.

imported_translations

integer

Translations stored from translations.

fingerprint_mismatches

list of strings

Keys whose sent fingerprint differs from Ponyglot’s.

A snapshot with duplicate segment keys is rejected with 422. Pushing the same snapshot again changes nothing, so retries are safe.

PUT /units/batch

Push up to 100 units: {"units": [snapshot, snapshot]}, each as in PUT /units. Each unit is applied on its own; one failure doesn’t block the others.

Response 200: {"results": [item, item]}, in the order of the request. Each item has external_key, ok (boolean), and either result (as in PUT /units) or error (a message).

DELETE /units/{external_key}

The object was deleted on the site. Always 204, also for unknown or already deleted units. Pushing the same key later restores the unit.

GET /units/{external_key}/status

The segment × language matrix of one unit. 404 for unknown or deleted units.

Field

Type

Description

external_key, label, path, adapter

string

The unit.

dashboard_url

string

The unit’s page in the dashboard.

last_synced_at

time or null

The last push.

segments_total

integer

Live segments.

segments_needing_work

integer

Segments that aren’t ok in at least one language.

languages

list

Per target language of the unit: code, ok, stale, review, missing, attention, and attention_url: the review queue, filtered to this unit’s translations held back by QA in this language.

segments

list

Per segment: key, field, kind, parent_key, position, states (language → state).

Connectors link attention_url where translations are held back: they are the only ones that need a decision in the dashboard.

GET /units

The units with something not up to date, ordered by id, for overviews of what needs attention.

Parameter

Description

prefix

Only units whose external_key starts with it.

language

Only this target language.

cursor

next_cursor of the previous page. 422 if invalid.

limit

1–500, default 100.

Field

Type

Description

units

list

Per unit: external_key, label, path, adapter, last_synced_at, dashboard_url, and languages: only the languages with work, each with code, missing, stale, review, attention and attention_url (set where translations are held back).

next_cursor

string or null

Pass as cursor for the next page; null on the last page.

In the QA mode editors fix, translations with QA errors are delivered to editors: they count as review.

Translation jobs

POST /jobs

Request translation. Jobs run in the background, one at a time per site, in the order requested.

Request field

Type

Description

type

string

delta (default): segments of the whole site that are missing, stale or rejected. translate: the same, limited to units.

units

list of external keys

Required for translate; up to 1,000.

prefix

string

delta only: units whose external_key starts with it, e.g. modeltranslation:shop.product: (all objects of one model).

languages

list of language codes

Default: all writable target languages. Other languages are left out.

force

boolean

Also retranslate segments that are approved, in review or held back by QA.

confirm

boolean

Confirm a job that needs it (see 409).

estimate_only

boolean

Only return the estimate; create nothing.

Responses:

  • 202 with the job (see below).

  • 200 with an estimate, if estimate_only is set:

    Field

    Type

    Description

    languages

    list of strings

    The job’s languages.

    estimated_segments

    integer

    Segments to translate.

    estimated_characters

    integer

    Billable characters: without translation memory hits and repeats.

    estimated_tm_segments

    integer

    Segments the translation memory covers.

    cost

    object

    The cost estimate, see below.

    An estimate doesn’t check the subscription. Without one, the cost’s volume fields are empty or zero.

  • 402 or 409, see Billing errors.

  • 422 if translate has no units, or if none of the languages is a writable target language.

Cost estimate (cost, and estimate in 409):

Field

Type

Description

characters

integer

Billable characters of this job.

included_characters

integer or null

Included per month; null if not metered.

used_characters

integer

Billable characters used this month so far.

reserved_characters

integer

Still expected by queued and running jobs requested earlier.

remaining_characters

integer or null

Included characters left after used and reserved ones.

overage_characters

integer

Characters of this job beyond the included volume.

overage_cents

integer

Estimated charge for them, in cents, billed after the month.

currency

string

EUR.

requires_confirmation

boolean

Whether the job must be sent with confirm: true.

Billing errors

402 and 409 have this body:

Field

Type

Description

detail

string

A message to show to the user.

code

string

subscription_required, subscription_inactive, site_not_covered (all 402) or confirmation_required (409).

estimate

object or null

For 409: the cost estimate.

A job needs confirmation if it has more than 100,000 billable characters, or if it would use characters beyond the included volume. Show the estimate, then repeat the request with "confirm": true. See Request translations.

The job

POST /jobs (202), GET /jobs, GET /jobs/{id} and POST /jobs/{id}/cancel return jobs:

Field

Type

Description

id

integer

The job’s ID.

type

string

delta or translate.

status

string

queued, running, succeeded, failed, cancelled.

languages

list of strings

Target languages.

units

list of strings or null

For translate jobs.

force

boolean

As requested.

estimated_segments

integer

Segments to translate, estimated when requested.

estimated_characters

integer

Billable characters, estimated.

estimated_tm_segments

integer

Segments the translation memory covers, estimated.

done_segments

integer

Translated so far.

failed_segments

integer

Not translated, see errors.

qa_failed_segments

integer

Translated but held back by QA errors.

characters

integer

Billed: source characters sent to engines.

tm_segments, tm_characters

integer

Covered by the translation memory; not billed.

reused_segments, reused_characters

integer

Repeats of a text within this job; not billed.

errors

list

Per problem: language, message, segments.

created_at, started_at, finished_at

time or null

Timestamps.

A job that translated nothing because every segment failed ends as failed.

GET /jobs

The 20 most recent jobs of the site. With ?active=true: only queued and running jobs.

GET /jobs/{id}

One job. 404 for jobs of other sites.

POST /jobs/{id}/cancel

Cancel a queued or running job. Segments that are already translated are kept. Returns the job.

Results

GET /results

Machine translations ready to be written as drafts or suggestions. Query parameters: cursor (from next_cursor), limit (1–500, default 100) and unit (an external_key: only that unit’s results, e.g. for an editor looking at one page).

Field

Type

Description

results

list

See below.

next_cursor

string or null

Pass as cursor for the next page; null on the last page.

Each result:

Field

Type

Description

id

string

Opaque result ID, e.g. r_5012. Store it with the draft.

unit

string

The unit’s external_key.

key

string

The segment’s key.

language

string

Target language.

text

string

The translation.

format

string

Same as the source segment.

source_fingerprint

string

Fingerprint of the source text it translates.

origin

string

deepl, llm, tm_exact, or human (fixed in the review queue).

engine

string

deepl, llm, tm (translation memory) or editor.

job_id

integer or null

The job that produced it.

qa

list

QA warnings: code, severity, message (see QA checks).

Only translations of the current source text are listed. Results stay listed until they are acknowledged. An invalid cursor gets 422.

POST /results/ack

{"ids": ["r_5012", "r_5013"]} after writing the drafts. Returns {"acknowledged": 2}: the number of results that moved to in review. Unknown, superseded and already acknowledged IDs are skipped.

POST /reviews

Report editors’ decisions.

Field

Type

Description

reviews

list, required

1–500 reviews.

reviews[].id

string, required

The result ID of the draft.

reviews[].outcome

string, required

approved or rejected.

reviews[].text

string

Final text, if the editor changed it (approved only).

reviews[].reviewer

string

Who decided, up to 200 characters.

Response: updated (integer) and ignored (the IDs that were unknown, superseded or not waiting for review).

  • approved: the translation is approved. A changed text is stored with origin human. Approved texts feed the translation memory.

  • rejected: the segment counts as missing again and is translated by the next job.

A translation discarded in the dashboard while it waited in a draft still accepts its review until a newer translation replaces it: publishing the draft first counts as approval.

Result IDs

  • Result IDs are opaque strings with the prefix r_. Don’t parse them, and don’t substitute other IDs.

  • Every generated version has its own ID. A replacement never reuses an earlier ID, even when the text and the source are the same.

  • Acknowledging a superseded ID changes nothing; reviewing one returns it in ignored.

  • Numeric IDs are accepted in acknowledgements and reviews, but always ignored.