POST /api/executions
API
Você manda Python, ele roda isolado, você recebe o stdout e quanto custou. Uma execução não alcança nada que este deployment não tenha permitido: nenhum filesystem além de um /tmp em memória, nenhum processo, nenhum ambiente, e só os endpoints de rede que o deployment permite. Cada execução ganha um processo só dela, e esse processo é descartado depois — então nada que uma execução deixe para trás alcança a seguinte: nem arquivo, nem variável, nem módulo que ela importou. Duas execuções do mesmo programa partem da mesma folha em branco, e uma execução que falhe de qualquer maneira falha sozinha. O `POST /api/executions` roda código. O `GET /api/executions` lista as execuções que a sua chave ainda tem em andamento, e o `DELETE /api/executions/{id}` para uma — você precisa do primeiro para usar o segundo, porque o id de uma execução só volta quando ela já terminou. Os dois são escopados pela chave que iniciou: outra chave nunca enxerga, nem outra chave da sua conta.
Schema para máquinas: /api/openapi.json
curl https://abstra-sucuri.eastus.cloudapp.azure.com/api/executions \
-H "Authorization: Bearer $SUCURI_API_KEY" \
-d '{"code": "print(sum(range(1000)))"}'
Requisição
-
codestring obrigatório - O Python a executar.
-
stdinstring - Texto entregue ao stdin do programa, quebrado por newline numa fila de linhas.
input()tira uma linha por chamada e levanta EOFError quando a fila está vazia, como o CPython faz no fim do arquivo. O argumento opcional de prompt é escrito no stdout, como no CPython. -
files{path: contents} - Arquivos escritos no /tmp antes do run, para entradas inline. Contam no mesmo orçamento maxTmpBytes contra o qual o programa escreve, então não é possível pré-encher além do limite.
-
mountsarray - Filesystems a anexar. Veja abaixo. Sem esse campo, o run vê apenas /tmp.
-
maxTmpBytesinteger - Teto do /tmp, em bytes. É limitado ao máximo do servidor, então só consegue estreitar. O limite vale para o total guardado, não por arquivo.
-
modules{dotted.name: source} - Módulos importáveis extras. É assim que você entrega código que não quer embutir em code, sem fazer deploy de nada em lugar nenhum.
-
net["host:port"] - Endpoints de saída que este run pode alcançar. A correspondência é exata em host e porta: api.example.com:443 não libera subdomínio nem a porta 80. O teto é do deployment e este campo só consegue estreitá-lo; um endpoint fora do teto é descartado e recusado na hora de conectar. Sem o campo, o run recebe o teto inteiro. Quando o teto do deployment está vazio, nenhum run tem rede.
-
env{name: value} - Variáveis de ambiente que o run enxerga. Alimenta os.getenv e os.environ dentro do guest. Sem o campo, o run enxerga um ambiente vazio — nada é herdado do processo hospedeiro.
-
hostCall.urlstring - Para onde vai o sucuri.call(name, payload) de dentro do guest. O servidor faz POST de {name, payload} em JSON aqui e espera {ok: true, result} ou {ok: false, error} de volta. O guest nunca vê esta URL. Sem hostCall, sucuri.call levanta PermissionError. O tempo esperando o host não conta para limits.timeoutMs.
-
hostCall.headers{name: value} - Headers enviados em toda chamada ao host, tipicamente um bearer token por execução. Ficam com o servidor e nunca são mostrados ao guest.
-
limits.timeoutMsinteger - Teto de tempo de parede deste run. É limitado ao timeout do próprio deployment, então só consegue estreitá-lo.
-
limits.maxInstructionsinteger - Teto de instruções do run. Só estreita: o run para no menor entre este valor, o teto do servidor, o limite por run e o seu saldo restante, e o
statusvolta comohalted.
Montando um filesystem
Uma execução não tem filesystem próprio. Ela recebe um /tmp em memória, descartado quando termina, mais o que você montar. Um mount aponta para o seu bucket ou container e usa as suas credenciais, então o dado fica onde já está e nada é armazenado aqui. Um caminho sob nenhum mount é recusado, como toda capacidade que você não concedeu.
| provider | Endereçamento | Credenciais | Funciona com |
|---|---|---|---|
s3 |
bucket, region, endpoint | accessKeyId, secretAccessKey, sessionToken | AWS S3, Cloudflare R2, MinIO, Backblaze B2, DigitalOcean Spaces, Wasabi |
gcs |
bucket | accessKeyId, secretAccessKey | Google Cloud Storage, pela API compatível com S3 usando uma HMAC key |
azure-blob |
account, container | sasToken ou accessKey | Azure Blob Storage |
Campos de um mount
-
atstring obrigatório - Caminho absoluto onde o mount aparece, por exemplo /data. Não pode ser /tmp nem estar dentro dele, não pode conter .., e não pode estar dentro de outro mount. Mounts sobrepostos são recusados em vez de resolvidos por uma regra que você teria que adivinhar.
-
providers3 | gcs | azure-blob obrigatório - Com qual serviço falar.
-
bucketstring - Nome do bucket, para s3 e gcs.
-
account, containerstring - Storage account e container, para azure-blob.
-
prefixstring - Prefixado em toda chave, então um mount pode expor só uma subárvore do bucket. Com at: "/data" e prefix: "runs/42/", ler /data/in.csv busca a chave runs/42/in.csv.
-
regionstring - Região do SigV4. Padrão us-east-1, que é o que serviços compatíveis com S3 sem noção de região esperam ver.
-
endpointstring - Sobrescreve o endpoint do provedor. Necessário para R2, MinIO e B2. Tem que ser https e resolver para um endereço público; redirects não são seguidos.
-
readOnlyboolean - Recusa toda escrita, append, remoção e rename sob este mount. Barrado antes de qualquer requisição sair deste serviço, então um mount read-only nem chega a pedir.
-
credentialsobject - Enviadas por TLS, usadas no run, e nunca armazenadas, logadas nem devolvidas. Seu código não consegue lê-las: nenhuma syscall as expõe e elas não vão para o ambiente. Omita por completo para um bucket público.
Exemplos prontos
Seu primeiro run — sem storage, sem setup
Nada para configurar: /tmp é memória, um caminho relativo cai lá porque /tmp é o working directory, e o filesystem inteiro some quando o run termina. Tudo abaixo acrescenta storage a isto.
{
"code": "open('out.txt','w').write('hi')\nprint(open('out.txt').read())",
"files": { "/tmp/in.json": "{\"n\": 1}" },
"maxTmpBytes": 1048576
}
Alimentando o stdin
O input() tira uma linha do stdin por chamada e levanta EOFError quando não sobra nada, então um programa consegue ler até o fim da entrada como faria num terminal.
{
"code": "total = 0\nwhile True:\n try:\n total += int(input())\n except EOFError:\n break\nprint(total)",
"stdin": "1\n2\n3"
}
Ler do S3 e gravar o resultado de volta
Dois mounts: entradas read-only, saídas graváveis. O run nunca vê o seu bucket inteiro, só os prefixos que você montou.
{
"code": "import csv\nrows = list(csv.reader(open('/in/sales.csv')))\nopen('/out/count.txt','w').write(str(len(rows)))",
"mounts": [
{ "at": "/in", "provider": "s3", "bucket": "acme-data", "prefix": "2026-08/",
"region": "us-east-1", "readOnly": true,
"credentials": { "accessKeyId": "AKIA...", "secretAccessKey": "..." } },
{ "at": "/out", "provider": "s3", "bucket": "acme-results", "region": "us-east-1",
"credentials": { "accessKeyId": "AKIA...", "secretAccessKey": "..." } }
]
}
Cloudflare R2, MinIO, Backblaze B2
Qualquer coisa que fale S3 funciona definindo endpoint. Use region "auto" no R2.
{
"code": "print(open('/data/model.json').read()[:80])",
"mounts": [
{ "at": "/data", "provider": "s3", "bucket": "models", "region": "auto",
"endpoint": "https://abc123.r2.cloudflarestorage.com", "readOnly": true,
"credentials": { "accessKeyId": "...", "secretAccessKey": "..." } }
]
}
Google Cloud Storage
Pela API compatível com S3 do GCS, com uma HMAC key da sua service account. Sem fluxo OAuth para configurar.
{
"code": "open('/bucket/out.txt','w').write('done')",
"mounts": [
{ "at": "/bucket", "provider": "gcs", "bucket": "acme-exports",
"credentials": { "accessKeyId": "GOOG1...", "secretAccessKey": "..." } }
]
}
Azure Blob Storage
Um SAS token de container é a credencial mais estreita para entregar: limite ao container e deixe expirar.
{
"code": "import os\nprint(sorted(os.listdir('/models')))",
"mounts": [
{ "at": "/models", "provider": "azure-blob", "account": "acmestore",
"container": "models", "readOnly": true,
"credentials": { "sasToken": "?sv=2024-11-04&se=..." } }
]
}
Resposta
-
executionIduuid obrigatório - Este run.
-
statuscompleted | failed | halted obrigatório - halted significa que um limite parou o run: o teto de instruções, o timeout, o heap ou a profundidade de recursão. Seu código não consegue capturar.
-
stdoutstring obrigatório - Tudo que o programa imprimiu.
-
resultstring - O valor do último statement de expressão do
code, como orepr()imprime — a regra que uma célula de notebook segue.2 + 2devolve"4";x = 2 + 2não devolve nada, porque atribuição não é expressão;print(x)não devolve nada, porque avalia para None. Ausente sempre que não houver esse valor. Um objeto de classe sua aparece pelo__repr__dele, caindo em<ClassName object>quando não define nenhum. Limitado a 64 KiB — vejaresultTruncated. -
resultTruncatedbool - Presente e verdadeiro quando o
resultbateu no teto de 64 KiB e foi cortado. O corte é só do transporte: dentro da execução o valor estava inteiro, entãolen(repr(x))continua respondendo o tamanho real. Ausente significa que nada foi cortado. -
resultErrorstring - Presente quando a renderização do
resultlevantou — um__repr__seu que falhou. NÃO significa que a execução falhou: o programa terminou, ostatusécompleted, e só essa renderização não deu certo. Sem ele, um__repr__que levanta seria idêntico a uma classe que não define nenhum. -
errorstring - O erro de Python, quando o run levantou exceção.
-
instructionsinteger obrigatório - O que é cobrado. O mesmo programa na mesma entrada sempre cobra igual, então você consegue prever e conferir.
-
cpuMicrosinteger obrigatório - Microssegundos de CPU consumidos. Um diagnóstico, não a cobrança: varia com a máquina e com quem mais estiver nela.
O Python que você recebe
Todo módulo que este sandbox consegue importar, com o que cada um exporta. A lista é gerada do próprio registro do interpretador, então ela é o que o engine em execução resolve, e não uma promessa a respeito. Importar qualquer outra coisa levanta ModuleNotFoundError, que o seu código consegue capturar. O campo `modules` da requisição acrescenta o seu próprio Python por cima. A lista nomeia o que um módulo exporta, não o que cada nome faz: um método que o motor não implementa levanta AttributeError, que o seu código também consegue capturar, então dá para sondar um NOME antes de depender dele. A sintaxe é a exceção: um construto que o compilador não aceita reprova o envio inteiro antes de qualquer coisa rodar, então não dá para sondar em tempo de execução e nada é impresso nem cobrado — confira `status` igual a `failed` e leia o `error`. O `collections`, o mais procurado, está completo na superfície das mappings e do deque — todo método público que o CPython 3.14 dá a dict, defaultdict, Counter, OrderedDict e deque, mais os operadores deles. Um módulo precisa de aviso, e não de lista: o `random` é semeado por execução, então duas execuções do mesmo programa sorteiam sequências diferentes, como aconteceria no CPython. O `random.seed(n)` no seu código escolhe a sequência e a repete exatamente — é a única forma de pedir uma execução reproduzível, e tirar o `n` do `stdin` ou de um arquivo é como variá-lo a cada chamada sem mexer no programa. O que o gerador NÃO é: fonte de segredo. É um gerador pseudoaleatório comum, de estado de 64 bits, então use para simulação e dado de teste, nunca para token, chave ou qualquer coisa que precise ser imprevisível.
-
__future__ absolute_import,annotations,division,generator_stop,nested_scopes,print_function,unicode_literals,with_statement-
abc ABC,ABCMeta,abstractmethod-
asyncio Event,Lock,Queue,Semaphore,gather,run,sleep,wait_for-
base64 b64decode,b64encode-
bisect bisect,bisect_left,bisect_right,insort,insort_left,insort_right-
collections Counter,OrderedDict,defaultdict,deque,namedtuple-
contextlib - Fornecido como código Python.
-
contextvars ContextVar-
copy copy,deepcopy-
csv DictReader,DictWriter,reader-
dataclasses MISSING,asdict,astuple,dataclass,field,fields,is_dataclass,replace-
datetime date,datetime,time,timedelta,timezone-
decimal Decimal-
enum Enum,IntEnum,auto-
fractions Fraction-
functools partial,reduce-
hashlib md5,sha256-
heapq heapify,heappop,heappush,nlargest,nsmallest-
http client-
http.client HTTPConnection,HTTPSConnection-
inspect - Fornecido como código Python.
-
io StringIO-
itertools accumulate,chain,combinations,compress,dropwhile,filterfalse,groupby,islice,pairwise,permutations,product,starmap,takewhile,zip_longest-
json dump,dumps,load,loads-
logging CRITICAL,DEBUG,ERROR,Formatter,INFO,StreamHandler,WARNING,basicConfig,getLogger-
math ceil,e,factorial,floor,gcd,inf,isfinite,isinf,isnan,nan,pi,pow,sqrt-
operator add,attrgetter,itemgetter,methodcaller,mul,sub,truediv-
os environ,getcwd,getenv,listdir,mkdir,path,sep,stat,walk,write_text-
pathlib Path-
pickle dumps,loads-
random choice,choices,getrandbits,randbytes,randint,random,randrange,sample,seed,shuffle,uniform-
re findall,search,split,sub-
shlex join,quote,split-
socket AF_INET,AF_INET6,SOCK_DGRAM,SOCK_STREAM,socket-
statistics mean,median,mode,stdev,variance-
string Template,ascii_letters,ascii_lowercase,ascii_uppercase,digits,punctuation-
struct calcsize,pack,unpack-
subprocess check_output,run-
sucuri call-
sys byteorder,getsizeof,maxsize-
tempfile mkdtemp-
textwrap dedent,fill,shorten,wrap-
traceback extract_tb-
typing Any,Dict,FrozenSet,Generic,List,Literal,Optional,Set,Tuple,Type,TypeVar,Union,cast,dataclass_transform,get_args,get_origin,get_type_hints,runtime_checks-
typing_extensions Any,Dict,FrozenSet,Generic,List,Literal,Optional,Set,Tuple,Type,TypeVar,Union,cast,dataclass_transform,get_args,get_origin,get_type_hints,runtime_checks-
urllib parse,request-
urllib.parse quote,unquote,urlencode,urlparse-
urllib.request Request,urlopen-
weakref WeakValueDictionary,ref
O que é levantado
-
BaseException - Toda classe de exceção builtin do CPython 3.14 existe aqui com o mesmo nome e com as mesmas bases, então nomear uma no
exceptnunca custa um NameError e um handler escrito contra uma base dispara:except ArithmeticErrorpega um ZeroDivisionError,except LookupErrorpega um KeyError, eexcept ExceptionNÃO pega KeyboardInterrupt, SystemExit nem GeneratorExit. Quais delas o próprio motor levanta é outra pergunta — o resto desta seção cobre isso. ExceptionGroup e BaseExceptionGroup também existem, com.exceptions,.subgroup,.splite.derive, e oexcept*divide um grupo entre as clauses e relança o que nenhuma reclamou. -
type(e).__name__ - Capturável. Um builtin que falha levanta a classe que o CPython levanta, então o handler que você escreveria para ele dispara:
[].pop()é IndexError,[1].index(9)emin([])são ValueError,next()além do fim é StopIteration,ord('ab')esorted([1,'a'])são TypeError. Uma falha que nenhuma regra reconhece chega como RuntimeError — a rede, para que uma falha imprevista continue capturada em vez de escapar. -
TypeError: not iterable - Capturável. Um gerador é um iterável e é DRENADO onde um iterável é aceito, o que cobre
map,filter,zipeenumerate, já que cada um deles devolve um gerador:sorted(map(...)),dict(zip(...))e"".join(map(str, xs))rodam a fonte. Drenar consome, exatamente como no CPython, então uma segunda passada pelo mesmo objeto vem vazia. Ser iterável não é ser sequência:reversed,random.choice,bisecteurlencodemedem ou indexam o argumento, então um iterador pode ser recusado com TypeError onde uma lista dos mesmos itens é aceita. Envolver emlist(...)resolve a questão antes de ela aparecer. Oiné a exceção que NÃO drena: ele puxa só até a resposta, então3 in gerador_infinito()retorna e o que ele não puxou continua lá. Oio.StringIOtambém é iterável, LINHA a linha, movendo o cursor do próprio buffer — ler uma linha pelo iterador e lê-la comreadlinesão a mesma leitura. -
str.encode / bytes.decode - Capturável. Três codecs de texto estão implementados — utf-8, ascii e latin-1 — sob os apelidos que o CPython aceita para eles, e utf-8 é o padrão. Qualquer outro nome de codec levanta LookupError, que NÃO é ValueError, então um nome com erro de digitação não passa como valor ruim. Texto que o codec não representa levanta UnicodeEncodeError e bytes que ele não lê levantam UnicodeDecodeError, os dois com a mensagem do CPython e os dois capturáveis como ValueError. Política de substituição não é oferecida: o
errors=é recusado em vez de silenciosamente ignorado, então texto que não pode ser codificado levanta em vez de chegar corrompido. -
OSError - Capturável. Toda capacidade que o sandbox recusa e toda que falha: um caminho sob nenhum mount, uma escrita além do maxTmpBytes, um endpoint fora do teto, um subprocesso. Uma recusa é PermissionError, que é o que capturar quando a pergunta é se a capacidade foi concedida; o resto que o filesystem reporta chega como OSError. Capture OSError quando quiser os dois.
-
ModuleNotFoundError - Capturável. Import de um módulo que este sandbox não tem. É subclasse de ImportError, então
except ImportErrortambém pega, ee.nameé o módulo que faltou. -
EOFError - Capturável.
input()sem nada restando no stdin, exatamente onde o CPython levanta no fim do arquivo. -
status: failed - Na resposta. O programa levantou e nada capturou:
errorcarrega a exceção estdoutcarrega o que foi impresso antes dela. Código que não compila também é reportado aqui, e não cobra nada. -
status: halted - Não capturável. Um limite de recurso parou o run: o teto de instruções, o timeout de parede, o heap ou a profundidade de recursão. É a única coisa que o seu código não consegue capturar nem limpar depois — não há exceção, o run para. Tudo que foi executado até ali é cobrado.
Quanto custa uma chamada
A cobrança é em instruções. A interpretação conta uma por instrução, um builtin que itera conta por elemento, e uma chamada que bloqueia paga o preço fixo abaixo. Esperar em si é de graça por desenho, então uma resposta lenta não custa mais que uma rápida.
| Chamada | Instruções | O que cobre |
|---|---|---|
conectar socket | 50,000 | Abrir uma conexão: um connect de TCP, um handshake de TLS, ou um bind de UDP. |
send / recv de socket | 10,000 | Um envio ou um recebimento num socket aberto, de qualquer tamanho. |
leitura / escrita em mount | 20,000 | Uma ida e volta a um bucket ou container montado: ler, escrever, stat, listar. |
operação em /tmp | 500 | Uma operação no /tmp em memória, que é RAM: sem rede e sem disco. |
byte em /tmp | 1 | Por byte movido pelo /tmp, além do custo da operação em si. |
Uma chamada é cobrada quando é tentada, tendo ela sucesso, falhando ou sendo recusada — senão um programa sondaria o sandbox de graça. Código que não compila não executa nada e não cobra nada.
Nenhum run executa mais que 100,000,000 instruções, seja qual for o saldo. Passando disso ele é parado, e tudo que ele fez até a parada é cobrado.
Editor
Logado, o portal tem um editor de Python que roda programas no Sucuri (Rodar, ou Ctrl/⌘+Enter), depura e entende o código enquanto você digita. Execuções e sessões de debug são execuções de verdade, cobradas do seu saldo; o saldo atualiza assim que uma termina.
Debugger
-
Breakpointsclique na margem - Clique na margem ao lado de uma linha para parar ali; clique de novo para remover. Uma linha sem código próprio (em branco, um comentário) se liga à próxima linha que tem código, e o ponto vai para lá. Os breakpoints ficam salvos com o seu código no navegador.
-
Condições, contagem, logpointsbotão direito na margem - Texto puro é uma condição (
n > 10), avaliada no frame toda vez que a linha é alcançada.hit >= 3só para a partir da terceira passagem (== 3,% 2e as outras formas do VS Code também funcionam).log total vale {total}imprime a mensagem com as expressões entre chaves avaliadas, sem parar. -
Continuar, pausar, pararF5 · F6 · Shift+F5 - F5 inicia uma sessão de debug e continua uma parada. Pausar para um programa em execução na próxima linha. Parar encerra: a execução termina como halted, igual a uma execução cancelada.
-
Passar por cima, entrar, sairF10 · F11 · Shift+F11 - A mesma semântica do debugpy no VS Code: por cima não entra em chamadas, entrar entra na próxima chamada, sair volta para a linha da chamada no chamador.
-
Exceções - Uma exceção não capturada para o programa na linha que a levantou, com a pilha ainda disponível para inspeção. Marque "Parar em exceções levantadas" para parar também onde exceções são levantadas e capturadas (StopIteration e GeneratorExit, que são controle de fluxo, nunca param).
-
Pilha e variáveis - Clique num frame para ver seus locals e os globals, com a linha destacada. Listas, tuplas, sets, dicts, objetos e módulos se expandem. Dê duplo clique num valor para trocá-lo: o que você digitar é avaliado naquele frame.
-
Observar, console, hover - As expressões observadas são reavaliadas a cada parada. O console avalia qualquer expressão no frame selecionado, e
nome = valoratribui. Passar o mouse sobre um nome (ouobj.attr) com o programa parado mostra o valor. -
Cobrança e limites - Uma sessão de debug é uma execução: as instruções são medidas e cobradas exatamente como em
POST /api/executions, incluindo as que suas expressões observadas e o console rodam. O tempo parado não conta para o timeout. Uma sessão de debug por conta de cada vez, no máximo 15 minutos.
Language server
-
Diagnósticos - Erros de tipo, nomes indefinidos e chamadas erradas são sublinhados enquanto você digita. O que o Sucuri não consegue rodar também: importar um módulo da biblioteca padrão que a engine não tem e construções que o compilador dela recusa — marcados como
sucuri, antes de qualquer execução. -
AutocompleteCtrl+Espaço · . - Nomes no escopo, atributos depois de
., argumentos nomeados e módulos, com tipos e documentação. Completar um nome ainda não importado adiciona o import. -
Hover e assinatura - Passar o mouse mostra o tipo inferido e a documentação. Dentro dos parênteses de uma chamada, a assinatura aparece com o parâmetro atual destacado.
-
Inlay hints - Tipos inferidos de variáveis sem anotação e nomes de parâmetros nas chamadas aparecem inline, em cinza.
-
NavegaçãoF12 · Shift+F12 - Ir para definição, declaração e definição de tipo; encontrar todas as referências; as ocorrências do nome sob o cursor ficam destacadas.
-
RenomearF2 - Renomeia uma variável, função, classe ou parâmetro em todo lugar onde é usado, e só lá.
-
Correções rápidasCtrl+. - Correções que o language server oferece para um diagnóstico, como adicionar um import que falta.
-
FormataçãoShift+Alt+F - Formata o programa com o formatador do ruff (estilo Black).
-
Estrutura, dobras, seleçãoCtrl+Shift+O · Shift+Alt+→ - Vá para qualquer função ou classe; dobre blocos; expanda a seleção para a expressão, o statement e o bloco em volta.
-
Realce semântico - Cores a partir do que o programa significa, não de como ele parece: parâmetros, classes, funções, módulos e propriedades se distinguem.
-
Como funciona - O language server é o ty (da Astral, que faz o ruff), informado sobre a biblioteca padrão do Sucuri em vez da do CPython. Cada aba do editor tem seu próprio processo isolado; nada é executado e nada é cobrado. Até 3 abas por conta.
Respostas HTTP
-
400 - Corpo malformado, ou um mount que não pode ser aceito: ponto de mount inválido, dois mounts sobrepostos, ou um endpoint ao qual este serviço não se conecta.
-
401 - API key ausente ou inválida.
-
402 - Seu saldo está zerado ou negativo. O corpo traz uma URL topUp.
-
503 - Não conseguimos medir o run, então ele não executou e nada foi cobrado. Tente de novo.