Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Matte

Using the API

Updated

Matte's API is three routes. All three need a bearer credential in the Authorization header. Paths below are relative to your Matte environment's URL.

Method and path What it does
POST /v1/remove Submit an image. Returns 202 and a job id. With ?sync=1, converts during the request and returns the PNG.
GET /v1/jobs/{jobId} Report a job's status: queued, processing, done or failed.
GET /v1/jobs/{jobId}/result Download the finished PNG. Returns 409 if the job is not done yet.

The asynchronous flow

This is the default and the one to use in production.

  1. POST /v1/remove with the image. Matte checks the credential, saves the original to the bucket, records a job and puts it on the queue. It answers 202:

    {"envelope_version":1,"job_id":"01a0cb3d-484f-7194-82ce-f310e5938786","status":"queued"}
    
  2. Poll GET /v1/jobs/{jobId}. The status moves from queued to processing when a worker picks the job up, then to done or failed. A failed job carries an error message.

  3. When it says done, GET /v1/jobs/{jobId}/result returns the PNG with Content-Type: image/png.

With curl, using a credential held in the TOKEN shell variable:

curl -F [email protected] -H "Authorization: Bearer $TOKEN" https://your-matte.example/v1/remove
curl -H "Authorization: Bearer $TOKEN" https://your-matte.example/v1/jobs/<job-id>
curl -o out.png -H "Authorization: Bearer $TOKEN" https://your-matte.example/v1/jobs/<job-id>/result

The synchronous flow

Add ?sync=1 to POST /v1/remove and Matte converts the image inside the request. A success is 200 with the PNG as the body. The request stays open until the conversion finishes or reaches the conversion timeout, 120 seconds by default.

Use it only where holding a request open that long is acceptable. The machine-learning engine loads its model from disk on every conversion, so it is far slower than grabcut: on a small test image the project measured about 12 seconds for ml against a tenth of a second for grabcut.

Request fields

Send these as multipart form fields on POST /v1/remove:

Field Values Default
image The image file. Required. none
mode ml or grabcut ml (the server's default mode)
preset fast, balanced or quality balanced
model A model file name. Used in ml mode. the server's default model, isnet-general-use.onnx
edge_mode blur, bilateral or guided none
iterations A positive integer: the GrabCut iteration count. none
margin A positive integer: the GrabCut margin. none
sync 1 to convert during the request off

The image must pass Laravel's image validation rule. JPEG, PNG, GIF, BMP, WebP, AVIF, HEIC and HEIF are accepted; SVG is not. Matte sets no explicit size or dimension limit of its own.

Choosing a mode

  • grabcut is a classical algorithm. It needs no model file and starts almost instantly. It works best on a distinct subject against a contrasting background.
  • ml runs the server's machine-learning model. It handles harder edges, such as hair or busy backgrounds, and is much slower. It fails if the named model file is not present on the server.

Responses and errors

Status When
202 An asynchronous job was accepted.
200 A synchronous conversion succeeded, a status was returned, or a result was downloaded.
401 The credential is missing, wrong, revoked, expired, minted for the wrong purpose, or of a kind Matte does not admit.
404 No job has that id, or a finished job's result is no longer in storage.
409 You asked for a result before the job reached done. The body includes the current status.
422 The request is invalid: no image, an unknown option value, a malformed integer, or a client newer than the server (see Troubleshooting).
500 A synchronous conversion failed. The body carries the error message.
503 A synchronous request reached a machine where the binary cannot run.

Storage and repeated images

Originals are stored under originals/ and results under outputs/ on the bucket. A result's name is a hash of the input bytes plus the options, so sending the same image with the same options writes to the same object rather than piling up copies. The conversion still runs again each time. Matte never deletes originals or results; clear them from the bucket yourself if you need to.

Getting a credential

Matte's image routes accept only bearer credentials minted for the consumption purpose (which Matte registers as matte.remove), owned either by an installation (one installed copy of one consuming app) or by a signed-in user.

  • For a Laravel app on Laravel Cloud or Forge, attach it through Scalpels. Scalpels mints a credential for that app and writes it straight into the app's environment. See Connecting your apps.

  • For anything else, mint an installation credential inside the Matte environment. Run this there, for example with cloud command:run, replacing acme-crm with a name for the consuming app:

    php artisan bfc:credential:mint installation 'acme-crm' --kind=bearer --purpose=consumption --name='matte-acme-crm' --local
    

    The command prints the credential once and stores only a hash of it. Put it straight into the caller's secret manager; there is no way to read it back. A credential only works against the database it was minted in, so it must be minted in the deployed environment, not on your own machine.

To see which credentials exist, run php artisan bfc:credential:list --local in the environment; it never shows secrets. To cut one off, run php artisan bfc:credential:revoke <credential-id> --local. Revoking an installation credential cuts off that one consumer and nothing else.

Completion callbacks

Polling is the supported way to learn that a job has finished. Matte can also post a signed completion message, but only to a destination registered in the server's own configuration (matte-server.callback.destinations) in your fork. It never posts to a URL supplied in the request: callback_url is rejected, and callback_destination must name a registered destination and only works on asynchronous requests. Delivery is best effort and never changes the job's outcome. Setting a destination up end to end means changing your fork's configuration and installing a signing credential in the receiving app, and the project does not yet document that process, so start with polling.