Upload an extension
An unpacked Manifest V3 extension as a zip, at most 10 MB: the raw body, or multipart/form-data with the zip in a file field. It is checked before it is stored (400 invalid_extension says why): Manifest V3 only; no nativeMessaging, debugger, proxy, vpnProvider or enterprise.* permissions; no native programs or libraries, symbolic links or unsafe paths; at most 32 MB and 5000 files unpacked. Needs the plan feature extensions. 30 per hour per project; at most 100 extensions per project (409 limit_reached). The plan and the rate limit are checked before the body is read. On the operator's deny list: 403 extension_denied. With a console login, only from the platform's own sites (403 cross_site_request). An extension sees every page and every typed value in the sessions that use it. Start sessions with extensions: [id].
/v1/extensionsRequest
| Method and path | POST/v1/extensions |
|---|---|
| Base URL | https://api.boxline.dev |
| Authentication | Your API key in the x-api-key header (or Authorization: Bearer). The examples read it from BOXLINE_API_KEY. |
| Safe retries | Send an Idempotency-Key header to retry without creating anything twice. See Idempotency keys. |
| Rate limit | Yes. See Rate limits for the numbers and headers. |
Headers
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
Idempotency-Key | string | – | – | Makes a retry safe: the same key and body within 24 hours returns the first response (with Idempotent-Replayed: true) instead of creating something twice. 1 to 255 printable ASCII characters without spaces, e.g. a UUID. Keys belong to the project (a new API key is replayed only to its caller). At most 300 new keys per minute per project (429). 1 to 255 characters. |
Body
application/zip. Required.
# The zip of the extension's folder (manifest.json at its root), at most 10 MB.curl -X POST "https://api.boxline.dev/v1/extensions" \ -H "x-api-key: $BOXLINE_API_KEY" \ -H "content-type: application/zip" \ -H "Idempotency-Key: $(uuidgen)" \ --data-binary @my-extension.zipimport { Boxline } from "@boxline/sdk";const bx = new Boxline(); // reads BOXLINE_API_KEY and BOXLINE_API_URL// A file path, or the zip's bytes (Uint8Array, Buffer, ArrayBuffer, Blob). An Idempotency-Key is sent for you.const ext = await bx.extensions.upload("./my-extension.zip");console.log(ext.id, ext.name, ext.version, ext.permissions);from boxline import Boxlinebx = Boxline() # reads BOXLINE_API_KEY and BOXLINE_API_URL# A path, or the zip's bytes. An Idempotency-Key is sent for you.ext = bx.extensions.upload("my-extension.zip")print(ext["id"], ext["name"], ext["version"], ext["permissions"])Responses
201 Created
The stored extension.
Headers
RateLimit-Limit: Requests allowed in the current window of this route's limit. (Rate limits)RateLimit-Remaining: Requests left in the current window. (Rate limits)RateLimit-Reset: Seconds until the window starts again. (Rate limits)Idempotent-Replayed:truewhen this is the kept response of an earlier request with the same Idempotency-Key. (Idempotency keys)
Fields
| Name | Type | Default | Allowed values | Description |
|---|---|---|---|---|
id | string | – | – | |
name | string | – | – | From the manifest (resolved from _locales when it uses __MSG_…__). |
version | string | – | – | |
description | string | – | – | |
permissions | string[] | – | – | |
hostPermissions | string[] | – | – | |
sizeBytes | integer | – | – | Size of the zip. |
files | integer | – | – | |
sha256 | string | – | – | Of the zip. |
createdAt | string | – | – | Format: date-time. |
{ "id": "ext_Q2x9L0aPz7NmK3Jb", "name": "Work account", "version": "1.2.0", "description": "…", "permissions": [ "…" ], "hostPermissions": [ "…" ], "sizeBytes": 0, "files": 0, "sha256": "…", "createdAt": "2026-09-29T10:00:00.000Z"}Errors
Errors have a matching HTTP status and the same body. code is stable, so check it in your code; message is for people. See Errors and request ids.
| Status | Codes | When |
|---|---|---|
| 400 Bad Request | invalid_request, invalid_json, invalid_cursor, invalid_idempotency_key, invalid_extension, variables_with_extensions, terms_not_accepted, invalid_reset_token, invalid_confirmation_code, wrong_password, no_password, invalid_next, invalid_oauth_token | The request is not valid. |
| 401 Unauthorized | unauthorized | Missing or invalid API key. |
| 402 Payment Required | feature_not_in_plan, spend_limit, out_of_credit, plan_limit, spend_limit, out_of_credit, organization, project, plan, credit | The plan does not include this, a monthly allowance or spending limit is used up, the organization has no credit left, or the plan allows no more of this. spend_limit and out_of_credit carry error.details.limit: organization, project, plan or credit. |
| 403 Forbidden | project_suspended, organization_suspended, role_forbidden, forbidden, extension_denied, cross_site_request, console_login_required, impersonating, invite_email_mismatch, email_unconfirmed | Not allowed. |
| 409 Conflict | session_not_running, shell_disabled, browser_disabled, nothing_saved, not_resumable, invalid_state, conflict, email_taken, identity_in_use, provider_already_linked, last_sign_in_method, not_impersonating, machine_too_old, too_many_credential_values, limit_reached, terms_version_mismatch, no_project, last_owner, organization_not_empty, project_has_sessions, already_member, invite_pending, idempotency_in_progress | The current state does not allow it, or a request with this Idempotency-Key is still being answered. |
| 413 | payload_too_large | The request body is larger than the route allows. |
| 422 Unprocessable Content | idempotency_mismatch | This Idempotency-Key was already used for a different request. |
| 429 Too Many Requests | rate_limited, concurrency_limit | Too many requests, or the organization's concurrent sessions (its plan's) or the project's concurrencyCap are in use. |
{ "error": { "code": "not_found", "message": "session not found", "requestId": "req_4fJx0aQm2Lk9Pz7N" }}Learn more
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.